okstra 0.121.1 → 0.123.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 (67) hide show
  1. package/README.md +4 -2
  2. package/docs/architecture/storage-model.md +14 -0
  3. package/docs/architecture.md +38 -8
  4. package/docs/cli.md +45 -5
  5. package/docs/for-ai/README.md +2 -2
  6. package/docs/for-ai/skills/okstra-rollup.md +1 -1
  7. package/docs/for-ai/skills/{okstra-schedule.md → okstra-schedule-gen.md} +3 -3
  8. package/docs/project-structure-overview.md +4 -3
  9. package/docs/task-process/implementation-planning.md +33 -9
  10. package/docs/task-process/implementation.md +21 -2
  11. package/package.json +1 -1
  12. package/runtime/BUILD.json +2 -2
  13. package/runtime/agents/workers/claude-worker.md +1 -1
  14. package/runtime/bin/lib/okstra/usage.sh +3 -3
  15. package/runtime/prompts/launch.template.md +9 -6
  16. package/runtime/prompts/lead/adapters/claude-code.md +104 -0
  17. package/runtime/prompts/lead/adapters/codex.md +45 -0
  18. package/runtime/prompts/lead/adapters/external.md +46 -0
  19. package/runtime/prompts/lead/context-loader.md +5 -5
  20. package/runtime/prompts/lead/convergence.md +11 -28
  21. package/runtime/prompts/lead/okstra-lead-contract.md +44 -94
  22. package/runtime/prompts/lead/plan-body-verification.md +50 -5
  23. package/runtime/prompts/lead/report-writer.md +59 -54
  24. package/runtime/prompts/lead/team-contract.md +33 -106
  25. package/runtime/prompts/profiles/_common-contract.md +3 -3
  26. package/runtime/prompts/profiles/_implementation-deliverable.md +3 -1
  27. package/runtime/prompts/profiles/_implementation-executor.md +6 -1
  28. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  29. package/runtime/prompts/profiles/final-verification.md +2 -0
  30. package/runtime/prompts/profiles/implementation-planning.md +14 -4
  31. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  32. package/runtime/prompts/wizard/prompts.ko.json +44 -0
  33. package/runtime/python/okstra_ctl/codex_dispatch.py +23 -1
  34. package/runtime/python/okstra_ctl/design_prep.py +1462 -0
  35. package/runtime/python/okstra_ctl/design_surfaces.py +243 -0
  36. package/runtime/python/okstra_ctl/final_report_schema.py +33 -1
  37. package/runtime/python/okstra_ctl/implementation_stage.py +35 -0
  38. package/runtime/python/okstra_ctl/incremental_carry.py +294 -21
  39. package/runtime/python/okstra_ctl/incremental_scope.py +51 -5
  40. package/runtime/python/okstra_ctl/lead_runtime.py +4 -0
  41. package/runtime/python/okstra_ctl/material.py +1 -1
  42. package/runtime/python/okstra_ctl/model_discovery.py +98 -0
  43. package/runtime/python/okstra_ctl/models.py +8 -3
  44. package/runtime/python/okstra_ctl/render.py +81 -194
  45. package/runtime/python/okstra_ctl/report_views.py +22 -7
  46. package/runtime/python/okstra_ctl/run.py +53 -5
  47. package/runtime/python/okstra_ctl/team_reconcile.py +3 -1
  48. package/runtime/python/okstra_ctl/user_response.py +67 -2
  49. package/runtime/python/okstra_ctl/wizard.py +283 -3
  50. package/runtime/python/okstra_token_usage/report.py +11 -0
  51. package/runtime/schemas/final-report-v1.0.schema.json +339 -0
  52. package/runtime/skills/okstra-inspect/SKILL.md +2 -2
  53. package/runtime/skills/okstra-rollup/SKILL.md +1 -1
  54. package/runtime/skills/{okstra-schedule → okstra-schedule-gen}/SKILL.md +62 -20
  55. package/runtime/skills/okstra-user-response/SKILL.md +8 -6
  56. package/runtime/templates/reports/final-report.template.md +71 -4
  57. package/runtime/templates/reports/i18n/en.json +32 -1
  58. package/runtime/templates/reports/i18n/ko.json +32 -1
  59. package/runtime/validators/validate-run.py +515 -5
  60. package/runtime/validators/validate-schedule.py +11 -7
  61. package/runtime/validators/validate_session_conformance.py +58 -33
  62. package/src/cli-registry.mjs +14 -0
  63. package/src/commands/inspect/design-prep.mjs +23 -0
  64. package/src/commands/inspect/stage-map.mjs +100 -0
  65. package/src/commands/lifecycle/install.mjs +3 -0
  66. package/src/commands/lifecycle/uninstall.mjs +8 -23
  67. package/src/lib/skill-catalog.mjs +10 -1
@@ -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
 
@@ -57,37 +51,35 @@ The prompt MUST include, in this order at the top:
57
51
  has been resolved by the lead from project.json / global config
58
52
  before the dispatch is constructed. The worker copies this verbatim
59
53
  into `data.json.meta.reportLanguage`.
60
- 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.`
54
+ 12. For implementation-planning runs: a literal block listing the 10 required English section headings (`Option Candidates`, `Trade-off`, `Recommended Option`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `User Approval Request`, `Plan Body Verification`, `Implementation Design Preparation`). The writer uses these exact substrings as section headings (Korean translation in parentheses is allowed), and the `Plan Body Verification` section carries its required `Gate result:` line.
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.
88
77
 
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.
78
+ For an implementation-planning run, the Report writer worker owns the Phase 6 design assessment snapshot: it writes `designPreparation` and every stage's `designSurfaceCoverage` into data.json from the detector output and consolidated plan. It does not create user inputs, consume a user answer as if it were part of that snapshot, or materialize `design-prep-requests/`; `schemas/final-report-v1.0.schema.json` and `validators/validate-run.py` `_validate_design_prep_contract` enforce the snapshot shape, detector coverage, and references.
79
+
80
+ 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.
81
+
82
+ 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
83
 
92
84
  ```bash
93
85
  okstra token-usage \
@@ -97,7 +89,9 @@ The four steps below MUST execute in this exact order. Reordering them is the re
97
89
  ```
98
90
 
99
91
  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:
92
+
93
+ For implementation-planning, this Phase 7 canonical render calls `materialize_design_prep_requests()` after token substitution and creates deterministic request files only for `provisional` / `blocked` items. Later answers are append-only user-input sidecars; request generation and user input never rewrite the assessment fields, so the source report remains immutable as the design-input snapshot after this render. `validators/validate-run.py` `_validate_design_prep_requests` enforces request existence, canonical path, content, and assessment fingerprint.
94
+ 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
95
 
102
96
  ```bash
103
97
  okstra render-views \
@@ -109,8 +103,8 @@ The four steps below MUST execute in this exact order. Reordering them is the re
109
103
  - implementation-planning 보고서에는 본문 끝에 **Plan Approval** 섹션(구현 옵션 `<select>` + 승인 체크박스)이 렌더된다 — §1 `Blocks: approval` 행이 미해소면 disabled. 승인을 체크하고 Export 하면 sidecar 본문에 `## APPROVAL` 블록이 실리고, implementation 시작 마법사의 approve-confirm 단계가 그것을 감지해 사용자 확인 후 기존 `--approve` / `--implementation-option` 경로로 적용한다.
110
104
  - 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
105
 
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.
106
+ 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.
107
+ 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
108
 
115
109
  ```bash
116
110
  okstra spawn-followups \
@@ -126,15 +120,14 @@ The four steps below MUST execute in this exact order. Reordering them is the re
126
120
  - 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
121
  - 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
122
  - **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:
123
+ After the spawner, the report writer MUST append one row per newly spawned task-key with its entry command:
130
124
 
131
125
  ```
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>`
126
+ - 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
127
  ```
134
128
 
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).
129
+ The status file is written after routing and follow-up persistence completes.
130
+ 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
131
 
139
132
  ## Final Report Structure
140
133
 
@@ -142,13 +135,15 @@ The final report follows the structure encoded in `schemas/final-report-v1.0.sch
142
135
 
143
136
  ### Report Header
144
137
 
138
+ 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.
139
+
145
140
  ```markdown
146
141
  # <task-key> - Multi-Agent Cross Verification Final Report
147
142
  - Date: <ISO 8601 timestamp>
148
143
  - Task Key: <task-key>
149
144
  - 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)>`
145
+ - Report Owner: `<exact schema header.reportOwner compatibility value>`
146
+ - Report Author: `<exact schema header.reportAuthor compatibility value for the actual authorship path>`
152
147
  - Lead model: `<lead-model>`
153
148
  - Preparation Method: Final report authored by Report writer worker (or lead-authored fallback — record the documented dispatch failure reason here when applicable)
154
149
  ```
@@ -158,22 +153,19 @@ The final report follows the structure encoded in `schemas/final-report-v1.0.sch
158
153
  ```markdown
159
154
  | Agent | Role | Model | Status | 처리 토큰 | 환산 토큰 | 비용 (USD) | Duration | Summary of Key Findings |
160
155
  |-------|------|-------|--------|-----------|-----------|------------|----------|------------------------|
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 |
156
+ | <team-state.lead.agent> | <team-state.lead.role> | <model> | completed | <tokens> | <billable> | <cost> | <duration> | Final synthesis status |
157
+ | <team-state.workers[].agent> | <team-state.workers[].role> | <model> | <status> | <tokens> | <billable> | <cost> | <duration> | Key findings summary |
166
158
  ```
167
159
 
168
160
  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".
161
+ - 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.
162
+ - Every worker agent identifier and role derives from its persisted team-state entry; the common contract does not infer labels from provider names.
171
163
  - **처리 토큰** = `usage.totalTokens` (input + output + cache_creation + cache_read; the raw volume processed).
172
164
  - **환산 토큰** = `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
165
  - **비용 (USD)** = `usage.estimatedCostUsd`. For Codex/Antigravity workers that actually invoked the CLI, append `(+ CLI $X.XX)` from `usage.cliEstimatedCostUsd`.
174
166
  - For Codex/Antigravity workers, append `(CLI: <cliTotalTokens>)` to the 처리 토큰 cell when `usage.cliTotalTokens` is set.
175
167
  - 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.
168
+ - Workers with status `not-run` or unavailable usage evidence show `--` for tokens/cost/duration; quote the `note` underneath the table if useful.
177
169
 
178
170
  ### Token Usage Summary Section
179
171
 
@@ -206,12 +198,12 @@ Token Summary Generation Rules:
206
198
  - **Grand total** (ko: `전체 합계`) row: `usageSummary.grandTotalTokens` / `usageSummary.grandBillableEquivalentTokens` / sum of `lead + claudeWorkers`.
207
199
  - **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
200
  - 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>`).
201
+ - If `lead` or any `worker.usage` records unavailable evidence, show `--` for that row and append a one-line note (`reason: <note>`).
210
202
  - 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
203
 
212
204
  ### Implementation-planning section heading contract (BLOCKING)
213
205
 
214
- When the run's `task-type` is `implementation-planning`, the final report MUST contain section headings whose **lines include each of the 9 literal English substrings below**. The validator (`validators/validate-run.py`) does plain substring matching on the report text — missing headings was a real, repeatedly observed failure mode caused by translating the headings to Korean.
206
+ When the run's `task-type` is `implementation-planning`, the final report MUST contain section headings whose **lines include each of the 10 literal English substrings below**. The validator (`validators/validate-run.py`) does plain substring matching on the report text and validates the design-preparation data contract — missing headings was a real, repeatedly observed failure mode caused by translating the headings to Korean.
215
207
 
216
208
  | # | Required substring | Recommended heading form |
217
209
  |---|--------------------|--------------------------|
@@ -224,11 +216,24 @@ When the run's `task-type` is `implementation-planning`, the final report MUST c
224
216
  | 7 | `Rollback` | `### Rollback Strategy (롤백 전략)` |
225
217
  | 8 | `User Approval Request` | Satisfied by the top-of-report `## User Approval Request (사용자 승인 게이트)` block. Do NOT recreate a `### 5.5.8 User Approval Request` body stub — the validator now fails reports that contain one. |
226
218
  | 9 | `Plan Body Verification` + `Gate result:` | `### Plan Body Verification (계획 본문 검증)` containing a `Gate result:` line — copy `templates/reports/final-report.template.md §5.5.9` verbatim. Validator checks both substrings. |
219
+ | 10 | `Implementation Design Preparation` | `### Implementation Design Preparation (구현 설계 준비)` — rendered from `implementationPlanning.designPreparation` by `templates/reports/final-report.template.md §5.5.10`. |
227
220
 
228
221
  The Korean translation in parentheses is optional but the English keyword is mandatory. The body of each section is written in the Report Language per the writing rules below. For non-`implementation-planning` runs, omit this entire block — these headings are NOT validator-checked for other task-types.
229
222
 
230
223
  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
224
 
225
+ ### Self-fix rewrite (plan-body self-fix 라운드)
226
+
227
+ lead 가 self-fix 라운드를 지시하면(대상 `P-*` 목록 + 각 결함 사유 전달), 지정된 planner-fixable 항목이 가리키는 **계획 섹션만** 교정한다 — 전체 draft 를 다시 쓰지 않는다:
228
+
229
+ - 경로 축약 → `<PROJECT_ROOT>` 기준 전체 상대경로로 교체(File Structure / Stepwise `files`).
230
+ - prose command → 작업 디렉터리·인자·task-type 이 포함된 실행 가능한 명령 시퀀스로 구체화.
231
+ - placeholder(`stage-<N>` 등) → 구체 파일명·재생성 계획으로 교체.
232
+ - requirement-coverage 매핑 오류 → 실재하는 option/stage/step 또는 XP 행으로 재매핑.
233
+ - missing/weak design-prep contract → kind-specific inline detail 또는 concrete `aiProposal` 을 가진 AI-prepared PREP item 으로 교정. 사용자·외부 권한 사실은 만들어내지 않고 `blocked` + request material 로 유지한다.
234
+
235
+ 교정 후 해당 `planItems[].selfFixNote` 에 `self-fixed in round <N>: <무엇을 고쳤는지>` 를 남기고, `planBodyVerification.selfFixRoundApplied = true` 로 표시한다. `needs-user-input` 항목은 절대 교정 대상이 아니다 — 그대로 clarification 으로 승격된다.
236
+
232
237
  ### Final-verification verdict token contract (BLOCKING)
233
238
 
234
239
  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 +252,13 @@ The final-report template `templates/reports/final-report.template.md` Section 7
247
252
 
248
253
  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
254
 
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`).
255
+ **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
256
 
252
257
  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
258
 
254
259
  ### Mandatory worker-results file (BLOCKING)
255
260
 
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:
261
+ 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
262
 
258
263
  ```
259
264
  runs/<task-type>/worker-results/report-writer-worker-<task-type>-<seq>.md
@@ -281,7 +286,7 @@ Every field MUST anchor its claim with at least one evidence reference — a `pa
281
286
 
282
287
  **Reader-facing prose MUST NOT cite a bare brief/worker-internal ID that this report never surfaces** — `RC-*` (reporter confirmations, defined in the brief), `RF-*` / `F-*` (findings, defined in worker-results) have no anchor in the final report, so a reader hits an opaque token with nothing to click. Either expand it inline (`the confirmed version target 1.27.47→1.27.48`) or, for an audit trail, namespace it (`claude:F-005`). **Enforced:** `_validate_no_opaque_id_references` fails a bare `RC-*` / `RF-*` / non-namespaced `F-*` appearing in `verdictCard` / `finalVerdict` / `rationale` / `clarificationItems[].statement`. (The renderer anchors + links in-report IDs of any digit width, so a surfaced `RC-4`-style id does resolve.) Do NOT restate the Verdict Card or the §5.4 trade-off matrix verbatim — this section is the *why*, in connected prose, that those tables compress.
283
288
 
284
- 0. **Clarification Response Carried In** — render this `## 0.` heading ONLY when `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}` is non-empty. Walk every `C-*` row of the prior report's `## 1. Clarification Items` table, reconcile against new evidence, and record the outcome (`resolved` / `obsolete`) with citation before drafting the verdict. When no carry-in path was provided, OMIT the `## 0.` heading entirely — the validator fails an empty Section 0 stub. When the lead ran `okstra incremental-scope` for this re-run, record its JSON verbatim into `implementationPlanning.incrementalDecision` (`mode`, `reverifyStages`, `carryStages`, `reason`); the renderer emits the `### 0.1 Incremental Re-Verification Scope` audit block from it, and the validator fails an `incremental`-mode run whose Section 0 omits that block. In `incremental` mode this run's `planItems` MUST carry every plan-item id from the re-verified stages forward with its updated verdict; if re-verification concludes a plan item should be REMOVED, that is a signal the answer's blast radius is not local — do not drop it here, tell the lead to abandon incremental and re-route to a FULL re-verification, because the carry merge only adds prior items and would resurrect the removed item's stale verdict.
289
+ 0. **Clarification Response Carried In** — render this `## 0.` heading ONLY when `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}` is non-empty. Walk every `C-*` row of the prior report's `## 1. Clarification Items` table, reconcile against new evidence, and record the outcome (`resolved` / `obsolete`) with citation before drafting the verdict. When no carry-in path was provided, OMIT the `## 0.` heading entirely — the validator fails an empty Section 0 stub. The lead calls `okstra incremental-scope` exactly once, combining answered-clarification stage impacts (`--impacted`) and changed PREP IDs (`--prep-items`); selected-option, Stage Map, or recommended-approach changes pass both CSVs empty to force full mode. Record that single decision JSON verbatim into `implementationPlanning.incrementalDecision` (`mode`, `reverifyStages`, `carryStages`, `reason`); the renderer emits the `### 0.1 Incremental Re-Verification Scope` audit block from it, and the validator fails an `incremental`-mode run whose Section 0 omits that block. In `incremental` mode this run's `planItems` MUST carry every plan-item id from the re-verified stages forward with its updated verdict; if re-verification concludes a plan item should be REMOVED, that is a signal the answer's blast radius is not local — do not drop it here, tell the lead to abandon incremental and re-route to a FULL re-verification, because the carry merge only adds prior items and would resurrect the removed item's stale verdict. After authoring the current data.json, call `okstra incremental-carry`, passing the decision's `carryStages` CSV to `--carry-stages` and its `reverifyStages` CSV to `--reverify-stages`. A `CarryError` means the stage/PREP ownership contract is unsafe: discard the partial merged output and route the run through full re-verification; never publish a partially merged report.
285
290
  1. **Cross Verification Results** — 4 categories (Full / Partial / Contested / Worker-Unique) when convergence is enabled, per `convergence`. Prepend the Round History sub-table (columns: `Round | inputQueueSize | resolvedCount | carriedForwardCount | dispatches | skippedWorkers`) plus a `round2SkippedReason: <value>` note, pulled verbatim from `convergence-<task-type>-<seq>.json`. Empty contested list renders as `- 합의 미달 항목 없음.`. Convergence-disabled runs use the legacy Consensus/Differences format and omit the round table.
286
291
  2. **Final Verdict** — `Direction` ∈ `continue-investigation` / `begin-implementation` / `approve` / `reject` / `hold`. **Verdict Token** is `not-applicable` for every task-type except `final-verification` — see "Final-verification verdict token contract" below for that case.
287
292
  3. **Evidence and Detailed Analysis** — primary evidence rows (file path, line, snippet); secondary evidence / alternate interpretations. If `reference-expectations.md` lists explicit expected values, record match/gap per row.
@@ -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