okstra 0.76.0 → 0.78.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 (65) hide show
  1. package/README.kr.md +5 -3
  2. package/README.md +5 -3
  3. package/bin/okstra +1 -117
  4. package/docs/contributor-change-matrix.md +12 -0
  5. package/docs/kr/architecture.md +1 -1
  6. package/docs/kr/cli.md +22 -5
  7. package/docs/pr-template-usage.md +3 -3
  8. package/docs/project-structure-overview.md +1 -1
  9. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +1 -1
  10. package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +493 -0
  11. package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +358 -0
  12. package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +134 -0
  13. package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +284 -0
  14. package/package.json +5 -2
  15. package/runtime/BUILD.json +2 -2
  16. package/runtime/DO_NOT_EDIT.md +21 -0
  17. package/runtime/agents/SKILL.md +3 -0
  18. package/runtime/bin/lib/okstra/cli.sh +5 -1
  19. package/runtime/bin/lib/okstra/globals.sh +1 -0
  20. package/runtime/bin/lib/okstra/usage.sh +6 -4
  21. package/runtime/bin/okstra-trace-cleanup.sh +16 -12
  22. package/runtime/bin/okstra.sh +1 -0
  23. package/runtime/prompts/launch.template.md +4 -13
  24. package/runtime/prompts/profiles/error-analysis.md +6 -0
  25. package/runtime/prompts/profiles/forbidden-actions.json +69 -0
  26. package/runtime/prompts/profiles/implementation.md +1 -8
  27. package/runtime/prompts/profiles/improvement-discovery.md +5 -0
  28. package/runtime/prompts/profiles/release-handoff.md +3 -17
  29. package/runtime/python/okstra_ctl/analysis_packet.py +17 -0
  30. package/runtime/python/okstra_ctl/codex_dispatch.py +1552 -0
  31. package/runtime/python/okstra_ctl/context_cost.py +1 -1
  32. package/runtime/python/okstra_ctl/dispatch_core.py +897 -0
  33. package/runtime/python/okstra_ctl/doctor.py +292 -0
  34. package/runtime/python/okstra_ctl/lead_events.py +129 -0
  35. package/runtime/python/okstra_ctl/lead_runtime.py +72 -0
  36. package/runtime/python/okstra_ctl/paths.py +3 -0
  37. package/runtime/python/okstra_ctl/pr_template.py +1 -1
  38. package/runtime/python/okstra_ctl/render.py +211 -29
  39. package/runtime/python/okstra_ctl/run.py +89 -18
  40. package/runtime/python/okstra_ctl/team.py +267 -0
  41. package/runtime/python/okstra_ctl/tmux.py +181 -10
  42. package/runtime/python/okstra_ctl/workflow.py +30 -71
  43. package/runtime/python/okstra_ctl/wrapper_status.py +55 -0
  44. package/runtime/python/okstra_token_usage/codex.py +12 -7
  45. package/runtime/python/okstra_token_usage/collect.py +243 -54
  46. package/runtime/python/okstra_token_usage/gemini.py +12 -7
  47. package/runtime/schemas/final-report-v1.0.schema.json +3 -1
  48. package/runtime/skills/okstra-convergence/SKILL.md +3 -0
  49. package/runtime/skills/okstra-report-writer/SKILL.md +3 -0
  50. package/runtime/skills/okstra-setup/SKILL.md +1 -1
  51. package/runtime/skills/okstra-team-contract/SKILL.md +12 -0
  52. package/runtime/validators/forbidden_actions.py +135 -0
  53. package/runtime/validators/validate-run.py +112 -22
  54. package/runtime/validators/validate_session_conformance.py +127 -5
  55. package/src/cli-registry.mjs +277 -0
  56. package/src/codex-dispatch.mjs +70 -0
  57. package/src/codex-run.mjs +68 -0
  58. package/src/doctor.mjs +130 -5
  59. package/src/install.mjs +123 -26
  60. package/src/paths.mjs +17 -3
  61. package/src/render-bundle.mjs +2 -0
  62. package/src/runtime-manifest.mjs +29 -0
  63. package/src/team.mjs +63 -0
  64. package/src/uninstall.mjs +27 -4
  65. /package/runtime/{skills/okstra-run/templates → templates/prd}/pr-body.template.md +0 -0
@@ -0,0 +1,284 @@
1
+ # 하네스 중립 tmux worker-dispatch 백엔드 설계
2
+
3
+ - 날짜: 2026-06-13
4
+ - 상태: 설계 초안 (rev2 — 1차 리뷰 반영: leadRuntime/dispatch-backend 축 분리, 완료 계약 명시, validator 범위 확장)
5
+ - 대상: okstra runtime, lead orchestration contract, worker dispatch backend
6
+ - 선행 문서: [`2026-06-12-codex-lead-adapter-design.md`](2026-06-12-codex-lead-adapter-design.md) — 이 문서는 그 **core + lead adapter split** 위에 **worker dispatch backend** 라는 직교 축을 추가한다. 선행 문서를 먼저 읽었다고 가정한다.
7
+
8
+ ## 1. 배경
9
+
10
+ okstra core 는 이미 비교적 하네스 중립적이다(선행 §1). 문제는 **lead 실행 계층과 worker dispatch 계층**이고, 둘이 현재 하나의 축(`leadRuntime`)에 얽혀 있다.
11
+
12
+ 오늘 lead+worker 는 두 레인으로 갈라져 있다.
13
+
14
+ | 레인 | dispatch | 완료 판정 | 관찰성 |
15
+ |---|---|---|---|
16
+ | `claude-code` | 네이티브 `TeamCreate`/`Agent(...)` ([agents/SKILL.md:212](../../../agents/SKILL.md:212)) | result-file 폴링([team-contract:124](../../../skills/okstra-team-contract/SKILL.md:124)) | Claude Teams split-pane + okstra trace pane |
17
+ | `codex` | 동기 `subprocess.run(worker.command)` ([codex_dispatch.py:1033](../../../scripts/okstra_ctl/codex_dispatch.py:1033)), render-only 게이트([run.py:2057](../../../scripts/okstra_ctl/run.py:2057)) | `returncode==0 and not missing_paths` ([codex_dispatch.py:213](../../../scripts/okstra_ctl/codex_dispatch.py:213)) | okstra trace pane 만, **순차** |
18
+
19
+ 사용자 목표: **tmux 세션이기만 하면 어떤 하네스(Claude Code / Codex / pi / omp / …)에서든, 사용자가 모든 worker pane 을 보면서 lead+worker 가 도는 중립 team layer.**
20
+
21
+ 검토(2026-06-13)에서 확인한 현황:
22
+
23
+ - worker **실행**: codex/gemini 는 이미 외부 CLI(`codex exec` / `gemini -p -`) via 래퍼 ([okstra-codex-exec.sh:297](../../../scripts/okstra-codex-exec.sh:297)). claude-worker 만 in-process Agent ([agents/SKILL.md:125](../../../agents/SKILL.md:125)).
24
+ - worker **완료 감지**: result-file 폴링 — 네이티브 `idle_notification` 을 **의도적으로 버린** 중립 설계 ([team-contract:143](../../../skills/okstra-team-contract/SKILL.md:143)).
25
+ - **관찰성**: trace pane(`tail -F`)은 okstra 가 `tmux split-window` 로 소유 ([okstra-codex-exec.sh:270](../../../scripts/okstra-codex-exec.sh:270)); worker-**compute** pane 은 하네스가 생성 ([_common-contract.md:36](../../../prompts/profiles/_common-contract.md:36)).
26
+ - **종료 상태 sidecar 가 이미 존재**: 래퍼가 `<prompt>.status.json` 에 `init`/`finish`(`exit_code`·`duration_ms`)/`timeout` 을 기록한다 ([okstra-codex-exec.sh:173-192](../../../scripts/okstra-codex-exec.sh:173), [:229-241](../../../scripts/okstra-codex-exec.sh:229), [:323](../../../scripts/okstra-codex-exec.sh:323)).
27
+
28
+ ### 1.1 핵심 결함 — `leadRuntime` 이 두 개념을 겹쳐 싣는다 (P0)
29
+
30
+ `leadRuntime` 은 현재 **lead 정체성**(누가 lead 인가)과 **worker dispatch 방식**(어떻게 spawn 하는가)을 동시에 결정한다.
31
+
32
+ - lead agent/role/label: `_lead_agent`/`_lead_role`/`_lead_agent_label` 이 `leadRuntime=="codex"` 만 분기 ([render.py:73-82](../../../scripts/okstra_ctl/render.py:73)).
33
+ - lead 회계/어댑터: `_lead_adapter` 가 `leadRuntime` 으로 `dispatchMode`+`sessionAccounting` 결정 ([render.py:85-97](../../../scripts/okstra_ctl/render.py:85)).
34
+ - conformance source: `event.lead_runtime != "codex"` 필터([validate_session_conformance.py:219](../../../validators/validate_session_conformance.py:219)), `lead_runtime=="codex"` 분기([:538](../../../validators/validate_session_conformance.py:538)).
35
+
36
+ 따라서 `leadRuntime="tmux"` 로 두면 lead 가 "Claude lead" 로 렌더되고 회계가 `claude-jsonl` 로 잘못 잡힌다(tmux≠codex 이므로 else 분기). **tmux 는 lead runtime 이 아니라 worker dispatch backend 다.** 두 축을 분리한다:
37
+
38
+ | 축 | 값 | 결정하는 것 | 저장 위치 |
39
+ |---|---|---|---|
40
+ | `leadRuntime` | `claude-code` · `codex` · **`external`(신규)** | lead 정체성·라벨·lead 회계·conformance source | manifest / team-state |
41
+ | **worker dispatch backend** | `team`(Claude Teams) · `cli-wrapper`/`mixed`(codex 현행) · **`tmux-pane`(신규)** | worker 를 어떻게 spawn·표시·완료판정 하는가 | `team_state.dispatchMode` + `workers[].backendType` (기존 필드 재사용) |
42
+
43
+ 두 축은 **직교**한다: `(codex, tmux-pane)`(codex lead + 보이는 pane), `(external, tmux-pane)`(pi/omp lead + pane), 나아가 `(claude-code, tmux-pane)`(Claude lead 가 Teams 대신 okstra pane 선택)도 표현 가능. 첫 cut 의 타깃은 `(external, tmux-pane)` 이고, 나머지 조합은 축 분리의 공짜 부산물로 두되 우선 구현하지 않는다.
44
+
45
+ > 단일-참조 주: 별도 `teamBackend` 필드를 새로 만들지 않고 **기존 `team_state.dispatchMode`/`workers[].backendType`** ([codex_dispatch.py:29-30](../../../scripts/okstra_ctl/codex_dispatch.py:29), `BACKEND_CLI_WRAPPER`/`BACKEND_MIXED`)에 값 `tmux-pane` 을 추가한다. `leadAdapter.dispatchMode`(render.py, `render-only`/`team`)는 lead 가 dispatch 하느냐 여부를 가리키는 **다른** 필드임에 유의 — 이 모호성도 본 설계가 §6.1 에서 정리한다.
46
+
47
+ ### 1.2 잔여 종속 + 하드코딩
48
+
49
+ - claude-worker in-process Agent (의도적으로 `claude --mcp-cli` 미사용, [agents/SKILL.md:125](../../../agents/SKILL.md:125)).
50
+ - Teams roster/`SendMessage`/`TeamDelete`([_common-contract.md:51](../../../prompts/profiles/_common-contract.md:51)), `~/.claude/teams/<team>/config.json` reconcile([team_reconcile.py:17](../../../scripts/okstra_ctl/team_reconcile.py:17)).
51
+ - 닫힌 enum `_ALLOWED_LEAD_RUNTIMES=("claude-code","codex")` ([run.py:270](../../../scripts/okstra_ctl/run.py:270)); dispatcher `leadRuntime=="codex"` 게이트([codex_dispatch.py:857](../../../scripts/okstra_ctl/codex_dispatch.py:857)); render-only 게이트([run.py:2057](../../../scripts/okstra_ctl/run.py:2057)).
52
+ - 고정 `SUPPORTED_CLI_WORKERS={codex,gemini}` ([codex_dispatch.py:25](../../../scripts/okstra_ctl/codex_dispatch.py:25)).
53
+ - codex 전용 lead 프롬프트([render.py:1749](../../../scripts/okstra_ctl/render.py:1749)).
54
+ - validator 의 codex-전용 분기 3곳: dispatchMode 요구([validate-run.py:405](../../../validators/validate-run.py:405)), token autofix skip([validate-run.py:1970](../../../validators/validate-run.py:1970)), conformance source([validate_session_conformance.py:538](../../../validators/validate_session_conformance.py:538)).
55
+
56
+ ## 2. 결정
57
+
58
+ 기존 core + adapter split 에 **`external` leadRuntime + `tmux-pane` dispatch backend** 를 추가한다.
59
+
60
+ 핵심 원칙:
61
+
62
+ 0. **두 축을 분리한다(P0).** `leadRuntime`(정체성/회계/conformance) ⟂ dispatch backend(spawn/표시/완료). 어느 쪽도 상대를 가정하지 않는다.
63
+ 1. **추출, not wrapping.** codex 의 dispatch 오케스트레이션([codex_dispatch.py](../../../scripts/okstra_ctl/codex_dispatch.py))을 backend-중립 core 로 추출하고 cli-wrapper/tmux-pane 가 같은 core 를 공유한다(§6.2). codex_dispatch 를 tmux 에서 호출하는 wrapping 금지.
64
+ 2. **okstra 가 worker-compute pane 을 소유.** `tmux split-window` 로 pane 생성, 거기서 worker CLI 실행(§6.3).
65
+ 3. **완료는 status sidecar 로 판정(P1).** tmux-pane 은 비동기라 `returncode` 가 없다 — 이미 존재하는 `<prompt>.status.json`(`exit_code`/`timeout`/`stage`)을 완료 계약의 SSOT 로 쓴다(§6.4).
66
+ 4. **one-shot / re-dispatch 모델 유지.** 살아있는 인터랙티브 teammate 금지 — 세션 격리가 교차검증의 correctness 안전장치(§7).
67
+ 5. **lead 는 하네스 shell 도구로 okstra CLI 호출**(네이티브 team 도구 0개). codex 가 `okstra codex-dispatch` 를 부르는 패턴([render.py:1764](../../../scripts/okstra_ctl/render.py:1764))의 일반화.
68
+
69
+ ## 3. 비목표
70
+
71
+ - **`leadRuntime` 에 `tmux` 를 넣지 않는다**(P0) — tmux 는 dispatch backend 값이다.
72
+ - Claude Teams primitive(`TeamCreate`/`SendMessage`/`TeamDelete`/FleetView) 1:1 재현 금지.
73
+ - **interactive long-running teammate 금지** — `send-keys` 입력 주입 + `capture-pane` 출력 파싱 양방향 대화는 비목표(세션격리 correctness 위배 + 화면 스크래핑 취약 + 큰 비용). worker 는 one-shot.
74
+ - `claude-code`/`codex` 레인 변경/폐기 금지(additive). 기존 사용자 무파손.
75
+ - `prepare_task_bundle`/profile/schema/validator 의 provider 별 fork 금지.
76
+ - tmux 외 멀티플렉서(zellij/screen/컨테이너) 추상화 선제 작성 금지(YAGNI).
77
+ - worker output contract/schema 신설 금지 — logical role/결과 contract 공유, dispatch backend 만 교체.
78
+ - 축 분리로 가능해지는 `(claude-code, tmux-pane)`/`(codex, tmux-pane)` 조합을 **첫 cut 에서 구현/테스트하지 않는다**(축만 열어둠).
79
+
80
+ ## 4. Adapter / backend 경계 (선행 §4 확장)
81
+
82
+ | 영역 | 소속 | 근거 / 변경 |
83
+ |---|---|---|
84
+ | `prepare_task_bundle`, `worktree*`, `workers.py`, `render*`, `schemas/`, `validators/`, `prompts/profiles/*` | **공통 core** | 선행 §4.1 |
85
+ | convergence algorithm, completion-polling protocol | **공통 core** | runtime/backend 무관 |
86
+ | dispatch 오케스트레이션(retry·lead-events·완료판정·report-writer 후처리) + backend registry | **공통 core (신규 추출)** | §6.2, codex_dispatch 추출 |
87
+ | pane orchestration (`okstra_ctl/tmux.py`) | **공통 core (신규)** | §6.3 |
88
+ | installed skills/agents, `AskUserQuestion`, `TeamCreate`/`Agent`, `claude --resume`, claude jsonl 회계, `TeamDelete`/`~/.claude/teams` | **claude-code adapter / `team` backend** | 선행 §4.2 |
89
+ | codex lead 라벨/프롬프트, codex rollout token 파싱 | **codex adapter** | 선행 §4.3 |
90
+ | `cli-wrapper` 동기 runner | **cli-wrapper backend** | codex 현행 |
91
+ | `external` lead 라벨/프롬프트/회계(`artifact-only`) | **external adapter (신규)** | §6.1 |
92
+ | pane split runner + sidecar 완료 판정 + async-wake + roster=team-state + teardown=kill-pane | **tmux-pane backend (신규)** | §6.3–6.6 |
93
+
94
+ ## 5. Execution modes — (leadRuntime, dispatch backend) 조합
95
+
96
+ | leadRuntime | backend | 상태 | 설명 |
97
+ |---|---|---|---|
98
+ | `claude-code` | `team` | 기존 | 네이티브 Teams + claude-jsonl conformance |
99
+ | `codex` | `cli-wrapper`/`mixed` | 기존(render-only→dispatch) | codex 순차 subprocess fan-out + event-log conformance |
100
+ | **`external`** | **`tmux-pane`** | **신규(첫 타깃)** | 임의 하네스 lead + okstra 소유 병렬 pane + sidecar 완료 + event-log conformance |
101
+ | `codex`/`claude-code` | `tmux-pane` | 축만 열림(미구현) | 부산물 — 첫 cut 비대상 |
102
+
103
+ `external` + `tmux-pane` 의 동작:
104
+
105
+ - **설치**: 공유 runtime/bin/templates/schema/validators 만, `~/.claude` 미터치(§9).
106
+ - **lead**: 임의 하네스 LLM 세션. okstra 가 lead 프로세스를 spawn 하지 않는다(codex 와 동일).
107
+ - **bootstrap**: `okstra render-bundle --lead-runtime external` 가 중립 lead 프롬프트 출력 → 하네스가 lead 컨텍스트에 주입(skill / system prompt / AGENTS.md).
108
+ - **dispatch**: lead 가 shell 로 `okstra team dispatch` 호출 → 공통 dispatch core 가 worker 마다 **자기 pane 에서 병렬 one-shot** 실행.
109
+ - **완료**: result-file + status-sidecar 폴링(§6.4) + async-wake(§6.6).
110
+ - **teardown**: `okstra team teardown` → `kill-pane`([okstra-trace-cleanup.sh](../../../scripts/okstra-trace-cleanup.sh) 재사용).
111
+
112
+ ## 6. 핵심 메커니즘
113
+
114
+ ### 6.1 `external` leadRuntime + 축 분리
115
+
116
+ - `_ALLOWED_LEAD_RUNTIMES` 에 `"external"` 추가([run.py:270](../../../scripts/okstra_ctl/run.py:270)).
117
+ - `_lead_agent`/`_lead_role`/`_lead_agent_label`/`_lead_adapter` 의 2-way(`codex` vs else)를 **3-way 또는 테이블 구동**으로([render.py:73-97](../../../scripts/okstra_ctl/render.py:73)): `external` → 중립 라벨("okstra lead", 하네스 비특정) + `sessionAccounting:"artifact-only"`.
118
+ - worker dispatch backend 은 `team_state.dispatchMode`/`workers[].backendType` 에 `tmux-pane` 추가([codex_dispatch.py:29-30](../../../scripts/okstra_ctl/codex_dispatch.py:29)). lead 프롬프트의 dispatch 가능 여부는 **backend 가 dispatch 가능한지**로 판단(아래 게이트).
119
+ - render-only 하드게이트([run.py:2057](../../../scripts/okstra_ctl/run.py:2057))와 dispatcher 게이트([codex_dispatch.py:857](../../../scripts/okstra_ctl/codex_dispatch.py:857))를 `leadRuntime` 직접 비교가 아니라 **"dispatch backend 가 구현되어 있는지"**로 일반화: `tmux-pane`/`cli-wrapper` = dispatch 허용, 미구현 backend = render-only.
120
+
121
+ ### 6.2 dispatch core 추출 + worker-backend 레지스트리 (P2 — 경계 명시)
122
+
123
+ `codex_dispatch.py` 는 단순 runner 가 아니라 오케스트레이터다. 추출 경계를 명시한다.
124
+
125
+ | 구성요소 | 위치(현재) | 행선지 |
126
+ |---|---|---|
127
+ | dispatch 루프·`_set_dispatch_mode` | [dispatch_plan:190](../../../scripts/okstra_ctl/codex_dispatch.py:190) | **공통 core** |
128
+ | retry 루프·`worker-dispatched` 이벤트·완료판정 | [_dispatch_worker_with_retry:207](../../../scripts/okstra_ctl/codex_dispatch.py:207) | **공통 core** (단, 완료판정은 §6.4 로 추상화) |
129
+ | prompt materialization | [~300](../../../scripts/okstra_ctl/codex_dispatch.py:300) | **공통 core** |
130
+ | report-writer 후처리(token/render-views/followups/validate) | [_post_process_report_writer_result:1036](../../../scripts/okstra_ctl/codex_dispatch.py:1036) | **공통 core** |
131
+ | runner: `cli-wrapper` 동기 실행 | [_run_cli_wrapper_worker:1018](../../../scripts/okstra_ctl/codex_dispatch.py:1018) | **cli-wrapper backend** |
132
+ | codex rollout token 파싱 | okstra_token_usage | **codex adapter** |
133
+ | runner: `tmux-pane` 비동기 split | (신규) | **tmux-pane backend** |
134
+
135
+ - 고정 `SUPPORTED_CLI_WORKERS`([:25](../../../scripts/okstra_ctl/codex_dispatch.py:25))를 `{worker_id → BackendDescriptor(wrapper, cli_bin, runner, usage_parser)}` 레지스트리로 일반화.
136
+ - **claude CLI backend 추가**: claude-worker 를 `claude --mcp-cli`(또는 비대화 exec) 래퍼로 실행 — okstra 가 의도적으로 안 쓰던 경로([agents/SKILL.md:125](../../../agents/SKILL.md:125))를 **dispatch backend≠team 일 때만** 채택. `(claude-code, team)` 은 계속 in-process Agent(무파손).
137
+ - codex/gemini 래퍼([okstra-codex-exec.sh](../../../scripts/okstra-codex-exec.sh)/[okstra-gemini-exec.sh](../../../scripts/okstra-gemini-exec.sh)) 그대로 재사용.
138
+
139
+ ### 6.3 okstra-owned worker-compute pane
140
+
141
+ - [okstra_ctl/tmux.py](../../../scripts/okstra_ctl/tmux.py)(현재 rerun 용 `new-session` 만)에 API 추가: `resolve_caller_pane / split_worker_pane / set_pane_title / tag_pane / capture_pane / kill_pane / list_run_panes`.
142
+ - 소스 로직은 검증된 shell 에서 **추출**(중복 금지): split+title+run-scoped tag = [okstra-codex-exec.sh:270-283](../../../scripts/okstra-codex-exec.sh:270), ancestor-PID 리졸브 = [tmux-pane.sh:21](../../../scripts/lib/okstra/tmux-pane.sh:21), tag 기반 kill = [okstra-trace-cleanup.sh](../../../scripts/okstra-trace-cleanup.sh).
143
+ - worker 마다 `split_worker_pane` 로 compute pane 생성 + 그 안에서 worker CLI 병렬 실행. pane 제목 = `<provider>-<role>`(executor/verifier 라벨은 [_implementation-executor.md:14](../../../prompts/profiles/_implementation-executor.md:14) 재사용). 사용자는 전 worker 를 라이브로 본다.
144
+
145
+ ### 6.4 완료 계약 — 동기 → 비동기 (P1, 핵심)
146
+
147
+ 현재 runner 는 **동기**다: `_run_worker → subprocess.run → CompletedProcess(returncode)` ([:1033](../../../scripts/okstra_ctl/codex_dispatch.py:1033)), 그리고 `returncode==0 and not missing_completion_paths` 로 완료 판정([:213](../../../scripts/okstra_ctl/codex_dispatch.py:213)). tmux-pane 은 `split-window` 가 **pane id 만** 즉시 반환하고 worker 는 detached 로 돈다 — `returncode` 가 없다.
148
+
149
+ → runner 인터페이스의 **형태를 바꾼다**: `run_worker()→CompletedProcess` 를 `spawn_worker()→WorkerHandle` + `await_worker(handle)→WorkerOutcome(terminal_status, exit_code)` 로 분리.
150
+
151
+ | backend | spawn | await |
152
+ |---|---|---|
153
+ | `cli-wrapper`(동기) | `subprocess.run` 실행 후 이미 종료된 handle | 즉시 반환(현행 의미 보존) |
154
+ | `tmux-pane`(비동기) | `split-window -P -F '#{pane_id}'` → `{paneId, statusSidecarPath, resultPath}` | (result 파일 존재) **AND** (status sidecar terminal) 폴링 |
155
+
156
+ - **status sidecar 가 완료 계약의 SSOT**: 래퍼는 이미 `<prompt>.status.json` 에 `init`/`finish`(`exit_code`·`duration_ms`)/`timeout` 을 EXIT trap·watchdog 으로 기록한다([okstra-codex-exec.sh:173-179](../../../scripts/okstra-codex-exec.sh:173), [:229-241](../../../scripts/okstra-codex-exec.sh:229), [:323](../../../scripts/okstra-codex-exec.sh:323), writer = [okstra-wrapper-status.py](../../../scripts/okstra-wrapper-status.py)). `await_worker` 는 이 sidecar 의 terminal `exit_code`/`timeout`/`stage` 를 읽어 동기 `returncode` 를 **대체**한다. 즉 신규 인프라가 아니라 **이미 기록되는 값을 완료 판정에 끌어쓰는 것**.
157
+ - 실패/타임아웃/retry 판정: `stage`+`exit_code`로 "래퍼 미시작 / CLI 실행했으나 산출물 없음 / 정상" 구분(이미 redispatch policy 의도, [:178-179](../../../scripts/okstra-codex-exec.sh:178)). retry 루프([:207](../../../scripts/okstra_ctl/codex_dispatch.py:207))는 `WorkerOutcome` 위에서 그대로 돈다.
158
+
159
+ ### 6.5 roster registry = team-state + dispatch 레코드 (P2)
160
+
161
+ re-dispatch/reverify 라운드/report-writer 는 worker 당 pane 이 **여러 개** 생긴다(§7). 단일 `paneId` 로는 표현 불가 → **dispatch 레코드 리스트**.
162
+
163
+ - top-level `workerDispatches[]`(또는 `workers[].dispatches[]`) 각 항목: `{role, kind: initial|retry|reverify-r<N>|report-writer, paneId, backendType, status, statusSidecarPath, resultPath, attempt}`.
164
+ - `teamBackend`/`dispatchMode` 는 team-state 상단에 기록(§1.1 표). `~/.claude/teams` 및 [team_reconcile.py](../../../scripts/okstra_ctl/team_reconcile.py) 의존 제거(claude-code/`team` 전용). teardown = 레코드의 paneId 들 `kill_pane` + team-state 정리.
165
+
166
+ ### 6.6 async-wake shim (유일한 환원 불가 하네스별 조각)
167
+
168
+ lead 는 "백그라운드 폴 → 완료시 깨어남" 으로 완료를 감지한다([team-contract:131](../../../skills/okstra-team-contract/SKILL.md:131)). 이 "end-turn→resume" primitive 만 하네스마다 다르다.
169
+
170
+ | 하네스 | primitive |
171
+ |---|---|
172
+ | Claude Code | `Bash(run_in_background:true)` + auto-resume |
173
+ | omp / pi | `bash(async:true)` + 자동 결과 전달 / `job poll` |
174
+ | Codex | (자체 비동기 메커니즘) |
175
+
176
+ - `okstra team await --run-dir <dir>` 가 §6.4 폴링을 수행하고, 하네스는 이를 자기 비동기 primitive 로 감싼다. okstra 는 폴링 로직(공통)만 소유, "백그라운드로 띄우는 법" 1줄만 lead 프롬프트가 하네스별로 지시.
177
+ - 긴 foreground blocking 을 죽이는 하네스([team-contract:142](../../../skills/okstra-team-contract/SKILL.md:142))용으로 `okstra team await` 는 주기적 stdout heartbeat 옵션을 둔다.
178
+ - tmux 미가용/blocking 미지원 시 → `cli-wrapper`(동기 subprocess) 로 graceful degrade(codex 현행 흡수).
179
+
180
+ ### 6.7 token accounting
181
+
182
+ - backend 별 usage 파서. codex rollout JSONL([okstra_token_usage/codex.py](../../../scripts/okstra_token_usage/codex.py))·gemini chat JSON 재사용. claude CLI backend 파서는 신규.
183
+ - lead 자체 token/cost 는 codex 와 동일하게 `leadUsage.source="unavailable"`.
184
+
185
+ ## 7. rework / 반복 처리 (1급 절)
186
+
187
+ 리뷰 중 제기된 질문 — "executor/worker 산출물을 verifier/validator 가 거부하면?" — 의 설계 답. **rework 는 살아있는 teammate 메시지가 아니라 lead 주도 re-dispatch / phase 재진입이며, 중립 모델에서 오늘과 동일하게 동작한다.**
188
+
189
+ 1. **verifier 간 불일치(run 내부, Phase 5.5)** → 새 one-shot reverify 워커 재dispatch([convergence:262](../../../skills/okstra-convergence/SKILL.md:262)), 재검증 항목은 새 프롬프트 본문([convergence:108](../../../skills/okstra-convergence/SKILL.md:108)). 중립 매핑: 라운드마다 새 pane split → §6.5 의 dispatch 레코드에 `reverify-r<N>` 항목 추가.
190
+ 2. **verifier 가 산출물 거부** → run 은 verdict 만 렌더하고 종료, in-run 수정 금지([final-verification.md:56](../../../prompts/profiles/final-verification.md:56)); Acceptance Blocker+Routing 이 **새 run** 입력([final-verification.md:54](../../../prompts/profiles/final-verification.md:54), [:40](../../../prompts/profiles/final-verification.md:40)). implementation 도 "기록 후 새 run 라우팅"([implementation.md:37](../../../prompts/profiles/implementation.md:37)). 중립 매핑: 새 run = 새 team/pane, 피드백은 이전 report 파일에서 읽음.
191
+ 3. **okstra validator 거부** → lead 가 산출물 수정 또는 report-writer 재dispatch.
192
+
193
+ **세션 격리는 correctness 요구사항이다** — "Session isolation … is the primary self-review safeguard: each verifier is a separate CLI invocation with its own context window"([_implementation-verifier.md:14](../../../prompts/profiles/_implementation-verifier.md:14)). 컨텍스트 유지 teammate 는 자기확증 편향 → 교차검증 무결성 훼손. §3 의 one-shot 비목표는 한계가 아니라 정확성 설계. 피드백 채널 = 영속 task identity([README §1](../../../README.md:29)) + 파일.
194
+
195
+ pane 부수효과 2:
196
+ - **라운드마다 pane 누적** → 기존 phase-start pane cleanup([_common-contract.md:36-37](../../../prompts/profiles/_common-contract.md:36))을 **라운드 경계**로 확장.
197
+ - **async-wake 공용** → §6.6 shim 하나가 초기/재dispatch 모두 커버.
198
+
199
+ ## 8. Contract changes
200
+
201
+ ### 8.1 manifest / team-state
202
+
203
+ ```json
204
+ {
205
+ "leadRuntime": "external",
206
+ "leadAdapter": { "name": "external", "dispatchMode": "tmux-pane", "sessionAccounting": "artifact-only" },
207
+ "dispatchMode": "tmux-pane",
208
+ "workerDispatches": [
209
+ { "role": "claude", "kind": "initial", "paneId": "%217", "backendType": "claude-cli",
210
+ "status": "completed", "statusSidecarPath": "...status.json", "resultPath": "..." }
211
+ ]
212
+ }
213
+ ```
214
+
215
+ ### 8.2 validator — codex-전용 분기 3곳을 backend/runtime-aware 로 일반화 (P1)
216
+
217
+ | 위치 | 현재 | 변경 |
218
+ |---|---|---|
219
+ | [validate-run.py:405](../../../validators/validate-run.py:405) | `leadRuntime=="codex"` → dispatchMode 요구, else → `teamCreate` 강제 | **dispatch backend≠`team`** 이면 dispatchMode 요구; `team` 일 때만 `teamCreate` 검사 |
220
+ | [validate-run.py:1970](../../../validators/validate-run.py:1970) | `leadRuntime=="codex"` → token autofix skip | **sessionAccounting=="artifact-only"**(codex+external) → skip |
221
+ | [validate_session_conformance.py:219](../../../validators/validate_session_conformance.py:219), [:538](../../../validators/validate_session_conformance.py:538) | `lead_runtime=="codex"` → event-log | **claude-jsonl 은 `(claude-code, team)` 에서만**; 그 외 → event-log + sidecar records (artifact-event runtime set 에 `external` 추가, 메시지 문구 일반화) |
222
+
223
+ 추가: dispatchMode enum 에 `tmux-pane` 등록(`CODEX_DISPATCH_MODES` → backend-중립 set 로 확장 또는 별도 set).
224
+
225
+ ## 9. Install / packaging (P2 — 전체 surface)
226
+
227
+ 첫 구현은 **디렉터리 이동 없이** 값/필드/명령만 추가. 손대는 파일:
228
+
229
+ - [install.mjs:90](../../../src/install.mjs:90) `INSTALL_RUNTIMES` set + `runtimeIncludesClaudeAssets`.
230
+ - [runtime-manifest.mjs:3-9](../../../src/runtime-manifest.mjs:3) `runtimeListForSelection` / `buildRuntimeManifest` asset 플래그.
231
+ - [doctor.mjs:68](../../../src/doctor.mjs:68) `requiredSkillNamesForRuntime`.
232
+ - [cli-registry.mjs:140](../../../src/cli-registry.mjs:140) 옆에 신규 `team`(dispatch/await/teardown) 명령 등록 + CLI help.
233
+ - uninstall / `installed-runtimes.json` 범위 / 관련 tests.
234
+
235
+ ## 10. 단계별 구현 계획 (리뷰 권장 순서)
236
+
237
+ ### Stage 1 — 축 분리 + render-only 중립 bootstrap (dispatch 미개방)
238
+ - `leadRuntime` += `external`; `_lead_*`/`_lead_adapter` 3-way; manifest `dispatchMode` 필드.
239
+ - 중립 lead 프롬프트 렌더(codex gate 형제, [render.py:1749](../../../scripts/okstra_ctl/render.py:1749) 옆).
240
+ - **`external` 도 render-only 유지** — `okstra team` 명령이 없으므로 dispatch 를 열지 않는다([cli-registry.mjs:140](../../../src/cli-registry.mjs:140) 미존재). 깨진 dispatch 프롬프트 방지.
241
+ - 테스트: render 3-way 단위, enum 정규화, manifest 직렬화. **pane/dispatch 없음.**
242
+
243
+ ### Stage 2 — pane orchestration API
244
+ - [okstra_ctl/tmux.py](../../../scripts/okstra_ctl/tmux.py) 에 §6.3 API 추출.
245
+ - 테스트: 명령 빌더 단위 + 실 tmux e2e(mktemp 세션, split/title/tag/list/kill 단언).
246
+
247
+ ### Stage 3 — dispatch core + 완료 계약 (한 묶음, dispatch 개방)
248
+ - codex_dispatch 추출(§6.2 경계) + backend registry + claude CLI backend.
249
+ - runner 인터페이스 `spawn/await` 분리 + status-sidecar 완료 계약(§6.4) + dispatch 레코드(§6.5).
250
+ - 신규 `okstra team dispatch` + `okstra team await` (+ `--dry-run`). 여기서 `external+tmux-pane` render-only 게이트 해제.
251
+ - tmux 미가용 시 cli-wrapper degrade.
252
+ - 테스트: fake worker CLI 로 pane 병렬/sidecar 완료/실패·타임아웃·retry e2e([codex-run-dispatch.integration.test.mjs:79](../../../tests-js/codex-run-dispatch.integration.test.mjs:79) 확장), backend registry 단위.
253
+
254
+ ### Stage 4 — roster/teardown + 라운드경계 cleanup
255
+ - team-state `workerDispatches[]` 기록; `okstra team teardown` = kill-pane; phase/라운드 경계 cleanup 확장(§7).
256
+ - 테스트: teardown·cleanup e2e.
257
+
258
+ ### Stage 5 — async-wake adapter + validator 일반화
259
+ - §6.6 wake shim(운영 하네스 1종부터) + §8.2 validator 3곳 일반화.
260
+ - 테스트: wake/ degrade 경로, validator backend-aware 분기.
261
+
262
+ ### Stage 6 — token accounting + packaging
263
+ - claude CLI usage 파서; §9 install/doctor/registry/uninstall.
264
+ - 테스트: collector backend별, doctor/ install runtime.
265
+
266
+ ## 11. 위험과 대응
267
+
268
+ | 위험 | 대응 |
269
+ |---|---|
270
+ | `leadRuntime` 과대적재(P0) | `external` runtime + dispatch backend 축 분리; render.py/validator 3곳을 runtime/backend-aware 로 |
271
+ | Stage 1 이 없는 `okstra team` 호출 프롬프트 생성(P1) | Stage 1 은 render-only 만; dispatch 는 명령 존재하는 Stage 3 에서 개방 |
272
+ | 비동기 완료 오판(P1) | status sidecar(`exit_code`/`timeout`/`stage`)를 완료 SSOT 로; result-file 단독 의존 금지 |
273
+ | validator 범위 과소(P1) | codex-전용 3분기를 backend/accounting 기준으로 일반화(§8.2) |
274
+ | 단일 paneId 로 reverify 표현 불가(P2) | `workerDispatches[]` 레코드 리스트(§6.5) |
275
+ | 추출 경계 모호(P2) | §6.2 표로 core/adapter/backend 행선지 명시; runner 인터페이스 형태 변경 명시 |
276
+ | packaging 누락(P2) | runtime-manifest/registry/uninstall/help/tests 포함(§9) |
277
+ | claude CLI backend 비용(in-process 이점 상실) | dispatch backend≠`team` 일 때만; `(claude-code,team)` 유지 |
278
+ | 교차검증 무결성 | 재검증 fresh one-shot 강제(§7), 인터랙티브 teammate 금지 |
279
+
280
+ ## 12. 결론
281
+
282
+ 가능하다. 단, "팀을 raw tmux 에 재발명"이 아니라 **"`leadRuntime` 에서 dispatch backend 를 떼어내고(P0), codex dispatch 를 backend-중립 core 로 추출하고, 비동기 완료를 status sidecar 로 판정(P1), worker-compute pane 을 소유"** 다. okstra core 가 이미 중립이고 codex adapter·status sidecar 가 살아있는 선례이므로, 작업은 새 발명이 아니라 경계 정리다.
283
+
284
+ **첫 PR = Stage 1**(축 분리 + `external` runtime + render-only 중립 bootstrap, **dispatch 미개방**). 이어 Stage 2(pane API). 실제 dispatch 와 완료 계약은 `okstra team` 명령이 생기는 Stage 3 에서 한 묶음으로 개방한다 — 그 전에는 어떤 단계도 없는 명령을 가리키는 lead 프롬프트를 만들지 않는다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.76.0",
3
+ "version": "0.78.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -26,6 +26,9 @@
26
26
  "scripts": {
27
27
  "build": "node tools/build.mjs",
28
28
  "prepack": "node tools/build.mjs",
29
- "test:js": "node --test tests-js/*.test.mjs"
29
+ "test:js": "node --test tests-js/*.test.mjs",
30
+ "test:py": "python3 -m pytest tests/",
31
+ "test:workflow": "bash validators/validate-workflow.sh",
32
+ "check": "npm run build && npm run test:js && npm run test:py && npm run test:workflow"
30
33
  }
31
34
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.76.0",
3
- "builtAt": "2026-06-11T20:04:51.254Z",
2
+ "package": "0.78.0",
3
+ "builtAt": "2026-06-14T07:09:18.701Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -0,0 +1,21 @@
1
+ # Do not edit runtime/
2
+
3
+ This directory is generated by `npm run build`.
4
+
5
+ Edit source files under:
6
+
7
+ - scripts/
8
+ - skills/
9
+ - agents/
10
+ - prompts/
11
+ - schemas/
12
+ - templates/
13
+ - validators/
14
+
15
+ Then run:
16
+
17
+ ```bash
18
+ npm run build
19
+ ```
20
+
21
+ Direct edits under runtime/ are overwritten.
@@ -220,6 +220,9 @@ These phases are governed by [okstra-team-contract](./skills/okstra-team-contrac
220
220
 
221
221
  Use agent and subagent names that map cleanly to the selected worker roles. Do not create ambiguous role names that differ from `Claude worker`, `Codex worker`, `Gemini worker`, or `Report writer worker`.
222
222
 
223
+ If the launch prompt contains `Tmux Worker Dispatch Gate`, Phase 3 is recorded as skipped with `reason: "tmux-pane"` and Phase 4 dispatch is performed only through `okstra team dispatch` followed by `okstra team await`. In this branch, calling `TeamCreate` or `Agent(...)` is a contract violation; worker completion is read from `workerDispatches[]` plus result files.
224
+
225
+
223
226
  ### Phase 4 / Phase 5 — Execution and error-log dump
224
227
 
225
228
  Spawn **analysis workers only** in the same turn (Phase 4 in Teams mode; Phase 5 with `run_in_background: true` and no `team_name` when Teams unavailable). Preserve exact roster, role labels, assigned models from the task bundle.
@@ -82,6 +82,10 @@ while [[ $# -gt 0 ]]; do
82
82
  REPORT_WRITER_MODEL_OVERRIDE="$(require_option_value --report-writer-model "${2-}")"
83
83
  shift 2
84
84
  ;;
85
+ --lead-runtime)
86
+ LEAD_RUNTIME="$(require_option_value --lead-runtime "${2-}")"
87
+ shift 2
88
+ ;;
85
89
  --executor)
86
90
  EXECUTOR_OVERRIDE="$(require_option_value --executor "${2-}")"
87
91
  shift 2
@@ -208,7 +212,7 @@ while [[ $# -gt 0 ]]; do
208
212
  printf ' hint: did you mean --task-id?\n' >&2
209
213
  ;;
210
214
  esac
211
- printf ' valid options: --render-only --resume-clarification --yes --workers --lead-model --claude-model --codex-model --gemini-model --report-writer-model --related-tasks --task-type --project-id --project-root --task-group --task-id --task-brief --directive --fix-cycle --clarification-response --approved-plan --approve --implementation-option --stage --qa-waiver --no-plan-verification -h|--help\n' >&2
215
+ printf ' valid options: --render-only --resume-clarification --yes --workers --lead-model --claude-model --codex-model --gemini-model --report-writer-model --lead-runtime --related-tasks --task-type --project-id --project-root --task-group --task-id --task-brief --directive --fix-cycle --clarification-response --approved-plan --approve --implementation-option --stage --qa-waiver --no-plan-verification -h|--help\n' >&2
212
216
  usage
213
217
  exit 1
214
218
  ;;
@@ -23,6 +23,7 @@ CLAUDE_MODEL_OVERRIDE=""
23
23
  CODEX_MODEL_OVERRIDE=""
24
24
  GEMINI_MODEL_OVERRIDE=""
25
25
  REPORT_WRITER_MODEL_OVERRIDE=""
26
+ LEAD_RUNTIME="claude-code"
26
27
  EXECUTOR_OVERRIDE=""
27
28
  CRITIC_CHOICE=""
28
29
  WORK_CATEGORY=""
@@ -3,7 +3,7 @@
3
3
  usage() {
4
4
  cat >&2 <<USAGE_EOF
5
5
  usage:
6
- $DISPLAY_COMMAND_NAME [--render-only] [--yes] [--no-plan-verification] --task-type <task-type> [--workers worker1,worker2] [--lead-model <model>] [--claude-model <model>] [--codex-model <model>] [--gemini-model <model>] [--report-writer-model <model>] [--executor claude|codex|gemini] [--critic off|claude|codex|gemini] [--related-tasks taskA,taskB] --project-id <project-id> [--project-root <path>] --task-group <task-group> --task-id <task-id> --task-brief <brief-path> [--directive <directive>] [--fix-cycle <yes|no>]
6
+ $DISPLAY_COMMAND_NAME [--render-only] [--yes] [--no-plan-verification] --task-type <task-type> [--workers worker1,worker2] [--lead-model <model>] [--claude-model <model>] [--codex-model <model>] [--gemini-model <model>] [--report-writer-model <model>] [--lead-runtime claude-code|codex] [--executor claude|codex|gemini] [--critic off|claude|codex|gemini] [--related-tasks taskA,taskB] --project-id <project-id> [--project-root <path>] --task-group <task-group> --task-id <task-id> --task-brief <brief-path> [--directive <directive>] [--fix-cycle <yes|no>]
7
7
 
8
8
  summary:
9
9
  $DISPLAY_TOOL_NAME prepares a task-keyed instruction bundle for Claude Code and launches an interactive Claude session by default.
@@ -27,7 +27,7 @@ required arguments:
27
27
  optional arguments:
28
28
  --project-root Absolute path to the target project root. Resolution order when omitted:
29
29
  (1) ancestor of cwd that contains .okstra/project.json,
30
- (2) `git rev-parse --show-toplevel` from cwd. Errors out if neither resolves.
30
+ (2) \`git rev-parse --show-toplevel\` from cwd. Errors out if neither resolves.
31
31
  --directive Free-form user-supplied directive carried into the run as a "## Directive" section
32
32
  inside instruction-set/analysis-material.md. Lead, workers, and skills (e.g. okstra-schedule)
33
33
  may treat this as a hard hint that overrides default heuristics. Use to express intent
@@ -87,13 +87,16 @@ options:
87
87
  exclusive with --clarification-response and --approved-plan.
88
88
  --yes Skip interactive prompting and confirmation. Requires all required arguments.
89
89
  --workers Comma-separated worker list for this run. Default: claude,codex,report-writer
90
- (Gemini worker is optional; add `gemini` explicitly, e.g. --workers claude,codex,gemini,report-writer)
90
+ (Gemini worker is optional; add \`gemini\` explicitly, e.g. --workers claude,codex,gemini,report-writer)
91
91
  --lead-model Model for Claude lead. Default: OKSTRA_DEFAULT_LEAD_MODEL or opus
92
92
  --claude-model Model for Claude worker. Default: OKSTRA_DEFAULT_CLAUDE_MODEL or opus
93
93
  --codex-model Model for Codex worker. Default: OKSTRA_DEFAULT_CODEX_MODEL or gpt-5.5
94
94
  --gemini-model Model for Gemini worker. Default: OKSTRA_DEFAULT_GEMINI_MODEL or auto
95
95
  --report-writer-model
96
96
  Model for report writer worker. Default: OKSTRA_DEFAULT_REPORT_WRITER_MODEL or lead model default
97
+ --lead-runtime Lead runtime adapter. Default: claude-code.
98
+ codex is currently render-only and records Codex adapter
99
+ metadata in prepared artifacts without dispatching workers.
97
100
  --executor Provider that performs the Executor role during --task-type=implementation.
98
101
  One of: claude | codex | gemini. Default: OKSTRA_DEFAULT_EXECUTOR or claude.
99
102
  The Executor is the only worker allowed to mutate project files; the other two
@@ -143,4 +146,3 @@ interactive behavior:
143
146
  If --yes is provided, $DISPLAY_TOOL_NAME skips prompting and confirmation and requires all required arguments up front.
144
147
  USAGE_EOF
145
148
  }
146
-
@@ -3,11 +3,12 @@
3
3
  # okstra-trace-cleanup.sh — close tmux panes created during okstra runs.
4
4
  #
5
5
  # Trace panes are `tail -F` siblings spawned by the codex/gemini wrappers
6
- # (`okstra-codex-exec.sh`, `okstra-gemini-exec.sh`). Each wrapper tags the pane
7
- # it spawns with a pane-level user option `@okstra_trace_run=<RUN_DIR>`, so the
8
- # panes are found server-wide by tag no tmux env var and no pane-id registry
9
- # are needed, and the run-scoped tag keeps concurrent okstra runs from closing
10
- # each other's panes.
6
+ # (`okstra-codex-exec.sh`, `okstra-gemini-exec.sh`). Worker-compute panes are
7
+ # tmux-pane backend siblings. Each wrapper/dispatcher tags the pane it owns with
8
+ # a pane-level user option (`@okstra_trace_run=<RUN_DIR>` or
9
+ # `@okstra_worker_run=<RUN_DIR>`), so panes are found server-wide by tag no
10
+ # tmux env var or pane-id registry is needed, and the run-scoped tag keeps
11
+ # concurrent okstra runs from closing each other's panes.
11
12
  #
12
13
  # Two invocation shapes:
13
14
  #
@@ -116,16 +117,19 @@ _tag_in_scope() {
116
117
 
117
118
  collect_okstra_panes() {
118
119
  local -a panes=()
119
- local pid tag title
120
+ local pid trace_tag worker_tag title
120
121
 
121
- # (1) Trace panes tagged in scope — found server-wide by tag, so no tmux env
122
- # var or pane-id registry is needed.
123
- while IFS=$'\t' read -r pid tag; do
122
+ # (1) Trace and worker-compute panes tagged in scope — found server-wide by
123
+ # tag, so no tmux env var or pane-id registry is needed.
124
+ while IFS=$'\t' read -r pid trace_tag worker_tag; do
124
125
  [[ -n "$pid" ]] || continue
125
126
  [[ "$pid" == "$lead_pane" ]] && continue
126
- _tag_in_scope "$tag" && panes+=("$pid")
127
- done < <(tmux list-panes -a -F '#{pane_id}'$'\t''#{@okstra_trace_run}' 2>/dev/null || true)
128
-
127
+ if _tag_in_scope "$trace_tag" || _tag_in_scope "$worker_tag"; then
128
+ panes+=("$pid")
129
+ fi
130
+ done < <(tmux list-panes -a \
131
+ -F '#{pane_id}'$'\t''#{@okstra_trace_run}'$'\t''#{@okstra_worker_run}' \
132
+ 2>/dev/null || true)
129
133
  # (2) Title-allowlisted worker-agent panes in the lead's session. Only for a
130
134
  # run (reap leaves these harness-owned panes to the harness). `list-panes -s
131
135
  # -t <pane>` resolves the session containing that pane, so the scan never
@@ -115,6 +115,7 @@ PY_ARGS=(
115
115
  [[ -n "${CODEX_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--codex-model "$CODEX_MODEL_OVERRIDE")
116
116
  [[ -n "${GEMINI_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--gemini-model "$GEMINI_MODEL_OVERRIDE")
117
117
  [[ -n "${REPORT_WRITER_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--report-writer-model "$REPORT_WRITER_MODEL_OVERRIDE")
118
+ [[ -n "${LEAD_RUNTIME-}" ]] && PY_ARGS+=(--lead-runtime "$LEAD_RUNTIME")
118
119
  [[ -n "${EXECUTOR_OVERRIDE-}" ]] && PY_ARGS+=(--executor "$EXECUTOR_OVERRIDE")
119
120
  [[ -n "${CRITIC_CHOICE-}" ]] && PY_ARGS+=(--critic "$CRITIC_CHOICE")
120
121
  [[ -n "${RELATED_TASKS_RAW-}" ]] && PY_ARGS+=(--related-tasks "$RELATED_TASKS_RAW")
@@ -1,7 +1,7 @@
1
1
  # okstra — {{TASK_KEY}}
2
2
 
3
- You are `Claude lead` for project `{{PROJECT_ID}}`.
4
- Invoke the `okstra` skill now. Read the manifests below for all task metadata, paths, model assignments, and worker roster.
3
+ {{LEAD_INTRO_LINE}}
4
+ {{LEAD_BOOTSTRAP_INSTRUCTION}}
5
5
 
6
6
  ## Progress reporting (BLOCKING)
7
7
 
@@ -28,13 +28,7 @@ Emit one `PROGRESS: <phase-id> <verb-phrase>` line as plain user-facing text at
28
28
  - All other paths in this prompt are relative to this root unless they begin with `/`.
29
29
  - When dispatching workers, you MUST include this absolute root in every worker prompt header so that workers do not depend on inherited cwd to resolve relative paths.
30
30
 
31
- ## Worker Prompt Files (not pre-rendered)
32
-
33
- - The `runs/<phase>/prompts/<worker>-worker-prompt-*.md` files referenced in `task-manifest.json → artifacts.workerPromptPathByWorkerId` are **NOT** rendered by the okstra runtime. Those paths are the *assigned* locations where the prompt history MUST be written at dispatch time.
34
- - Therefore: at the start of every phase the prompts/ directory is normally empty (or contains only previously-dispatched workers' files). This is expected. Do NOT narrate it as "missing", "누락", or "not yet rendered" — it just means dispatch has not happened yet.
35
- - Before dispatching any required worker, **you (the lead) construct the worker prompt and persist it to the assigned absolute path using `Write`** (per `okstra-team-contract` rule 6). Only after persisting do you call the worker subagent (Agent tool / Codex / Gemini wrapper). The wrapper subagents will also re-write the same file on their end; the double-write is intentional and idempotent.
36
- - Do not "check if the file exists and skip dispatch" — file presence is not a signal to skip. Worker selection and skipping rules come from team-state, never from prompts/ directory contents.
37
- - **Worker Preamble Path (single anchor — replaces inlined `[Required reading]` and `[Error reporting]` blocks).** Every worker prompt you construct MUST include the anchor header `**Worker Preamble Path:** {{WORKER_PROMPT_PREAMBLE_PATH}}`. The file at that path is the canonical SSOT for Required Reading + Error Reporting + Output sections; workers Read it end-to-end before producing output. Do NOT re-inline those blocks into worker prompt bodies — that is the legacy ~80-line dispatch boilerplate that this anchor is designed to replace.
31
+ {{WORKER_DISPATCH_GUIDANCE}}
38
32
 
39
33
  ## Manifests
40
34
 
@@ -43,10 +37,7 @@ Emit one `PROGRESS: <phase-id> <verb-phrase>` line as plain user-facing text at
43
37
  - Active run context: `{{ACTIVE_RUN_CONTEXT_RELATIVE_PATH}}`
44
38
  - Analysis packet: `{{ANALYSIS_PACKET_RELATIVE_PATH}}`
45
39
 
46
- ## Session
47
-
48
- - Session ID: `{{CLAUDE_SESSION_ID}}`
49
- - Resume: `{{CLAUDE_RESUME_COMMAND_RELATIVE_PATH}}`
40
+ {{LEAD_SESSION_BLOCK}}
50
41
 
51
42
  ## Run Paths
52
43
 
@@ -13,6 +13,12 @@
13
13
  - the reporter's symptom description in `Source Material` is the ground truth for what to reproduce. Do not paraphrase it when stating the symptom in the report; quote it.
14
14
  - any `intent-inference` augmentation that re-characterises the symptom (e.g. classifying "가끔 안 됨" as "intermittent failure on a specific code path") is a **hypothesis**, not a confirmed symptom. If `[CONFIRMED …]` appears on the matching `intent-check:` row, treat the confirmation as the symptom; otherwise, follow the precondition's `skipped` branch above and keep the inference labelled as hypothesis in the root-cause analysis.
15
15
  - `conversion-block:` rows mean the brief could not map a reporter statement to project vocabulary; never attempt to invent the missing mapping in this phase — the precondition above already handled them.
16
+ - Diagnosis loop:
17
+ - **Symptom lock:** state the reporter's symptom verbatim, then translate it into one observable failure condition. If the observable condition cannot be derived from the brief, make that the first blocker instead of guessing.
18
+ - **Reproduction status:** classify the run as `reproduced`, `not-reproduced`, or `blocked-before-repro`. Cite the command/log/file evidence used. If no command can be run safely in this phase, explain the read-only evidence path and the exact material needed next.
19
+ - **Falsifiable cause candidates:** every root-cause candidate must include supporting evidence, the strongest falsifying evidence checked, confidence, and the next diagnostic action that would disprove it. A candidate that cannot be falsified is too vague for this phase.
20
+ - **Sharp next diagnostic:** end with the single highest-value diagnostic command, log capture, or file inspection that should happen next, plus the expected signal that would confirm or reject the leading cause.
21
+ - **Fix-design boundary:** do not design the implementation fix beyond what is necessary to validate the cause. If the cause is credible, route to `implementation-planning` with the verified evidence; if the cause is still unclear, route to another `error-analysis` run with the next diagnostic.
16
22
  - Primary focus areas:
17
23
  - symptom and trigger clarification
18
24
  - root-cause candidates
@@ -0,0 +1,69 @@
1
+ {
2
+ "requirements-discovery": [
3
+ "source code edits of any kind",
4
+ "implementation planning or detailed design beyond what is required to choose the next phase",
5
+ "executing builds, migrations, deployments, or any state-mutating command",
6
+ "starting `error-analysis`, `implementation-planning`, or `implementation` inside this run (each must be a separate run, and `implementation` additionally requires an approved `implementation-planning` deliverable)"
7
+ ],
8
+ "improvement-discovery": [
9
+ "source code edits of any kind",
10
+ "implementation planning, root-cause analysis, builds, migrations, or deployments",
11
+ "starting `implementation-planning`, `implementation`, `error-analysis`, or any other lifecycle phase inside this run",
12
+ "generating candidates outside the lens whitelist (Lens enum violation rejects the report)",
13
+ "exceeding the candidate cap (absolute cap 12)",
14
+ "free external data fetch beyond the brief's Source Material or Phase 1.5 resolved scope",
15
+ "interpreting user phrases like `다음 단계 진행해` as authorisation to enter another phase"
16
+ ],
17
+ "error-analysis": [
18
+ "source code edits, refactors, or fix attempts",
19
+ "implementation design or planning artifacts",
20
+ "executing builds, migrations, deployments, or any state-mutating command",
21
+ "starting `implementation-planning` or `implementation` inside this run (each must be a separate run, and `implementation` additionally requires an approved `implementation-planning` deliverable)"
22
+ ],
23
+ "implementation-planning": [
24
+ "source code edits of any kind (Edit/Write on project source files is forbidden)",
25
+ "file writes outside the run`s artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`); in particular, do not write to `docs/superpowers/specs/` or `docs/superpowers/plans/`",
26
+ "executing builds, migrations, deployments, or any state-mutating command",
27
+ "starting `implementation` inside this run (must be a separate run authorised by an approved deliverable from this phase), even if the user says \"다음 단계 진행해\"",
28
+ "dispatching parallel sub-agents beyond the required worker roster (okstra owns worker fan-out)",
29
+ "leaving placeholders such as TBD / TODO / \"handle edge cases\" / \"similar to Option N\" in the report",
30
+ "delegating the self-review pass to a generic subagent — `Claude lead` must run it"
31
+ ],
32
+ "implementation": [
33
+ "any Edit/Write or state-mutating Bash before the pre-implementation gate passes (gate requires --approved-plan pointing to a final-report.md whose frontmatter has `approved: true`)",
34
+ "`git push` of any kind (including `--dry-run` against a real remote that produces side-effects), `npm publish` / `cargo publish` / `pip publish`, `gh release`, `docker push`",
35
+ "real database migrations, schema changes against shared environments, or writes to non-local datastores",
36
+ "production credentials, deploy commands, infra mutation (`terraform apply`, `kubectl apply` against non-local cluster, etc.)",
37
+ "external API write calls (POST/PUT/PATCH/DELETE) to third-party services other than localhost test fixtures",
38
+ "source edits or Bash mutations performed by any verifier role (`Gemini verifier`, `Codex verifier`, `Claude verifier` are read-only — recommend, do not apply)",
39
+ "dispatching parallel sub-agents beyond the required worker roster",
40
+ "silent scope expansion: every file edited outside the approved plan list MUST appear in the `Out-of-plan edits` block with rationale",
41
+ "leaving placeholders such as TBD / TODO / \"implement later\" / \"handle edge cases\" in newly-added lines of this run (check via `git diff <base>..HEAD | grep -E '^\\+[^+].*\\b(TBD|TODO|FIXME|XXX|implement later|handle edge cases|similar to|placeholder)\\b'`; pre-existing strings in untouched regions are out of scope)",
42
+ "lead substituting its own verdict when every required verifier (`Gemini verifier`, `Codex verifier`, `Claude verifier`) returned a non-result terminal status (`timeout`/`error`/`not-run`); in that case the run MUST end as `blocked` with routing recommendation back to `error-analysis`, never with a lead-only verdict",
43
+ "claiming rollback verification on a schema migration, config-format change, or any persisted-state mutation without a recorded dry-run of the rollback step and its captured exit code",
44
+ "declaring overall task acceptance — that is `final-verification` ownership; this phase reports only \"ready for final-verification\" or \"needs new planning loop\"",
45
+ "delegating the self-review pass to a generic subagent — `Claude lead` must run it"
46
+ ],
47
+ "final-verification": [
48
+ "source code edits, follow-up bug fixes, or scope expansion",
49
+ "state-mutating commands; only read-only execution of pre-existing test or validation commands is permitted",
50
+ "starting any follow-up phase inside this run; record findings and end the run"
51
+ ],
52
+ "release-handoff": [
53
+ "entering this phase when the cited final-verification `Verdict Token` is `conditional-accept` or `blocked`, or when no final-verification report is cited",
54
+ "local commit commands of any kind (`git add`, `git commit`, `git restore --staged`, `git stash`), and any direct `git merge` / `git rebase` run by the lead. The single exception is the merge commits `okstra handoff assemble` itself creates on the collector branch — the lead never merges by hand.",
55
+ "any git push variant that rewrites remote history, regardless of intent or whether the user said \"force it\": `git push --force`, `git push --force-with-lease`, `git push -f`, `git push +<refspec>`, or any other history-rewriting invocation",
56
+ "pushing directly to a base branch — i.e. `git push origin <branch>` where `<branch>` is `main`, `master`, `prod`, `preprod`, `staging`, `dev`, or the branch the user chose as the PR base in this run. The only permitted push target is the current feature branch.",
57
+ "bypassing repo safeguards: `--no-verify` / `-n` on `git push`, bypassing GPG signing, disabling safeguards via equivalent flags, or any hook bypass.",
58
+ "release-publishing commands: `gh release create`, `gh release edit`, `npm publish`, `cargo publish`, `pip publish`, `twine upload`, `docker push`, `terraform apply`, `kubectl apply` against any non-local cluster.",
59
+ "source-code edits, refactors, or any modification to files outside the run's own artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`). The diff being shipped MUST be exactly what the prior `implementation` run produced; release-handoff packages it, it does not re-author it.",
60
+ "executing any mutating command the user did NOT select. Examples: opening a PR when the user picked `local only`; pushing when the user picked `skip`; switching the PR base branch silently after the user already chose one.",
61
+ "retrying a failed git / gh command with weaker safety flags. If `git push` fails with non-fast-forward, the lead MUST stop, explain the failure to the user, and ask for instructions — it MUST NOT add `--force`.",
62
+ "`TeamCreate`, `Agent(...)` dispatch of any kind, or any other parallel sub-agent fan-out. This phase runs entirely under the Claude lead.",
63
+ "silently treating an unrecognised user reply as one of the menu options. If the user's answer does not match a presented choice, re-ask the question verbatim."
64
+ ],
65
+ "unknown": [
66
+ "any action that belongs to a different lifecycle phase",
67
+ "source code edits or state-mutating commands unless this task type explicitly authorises them"
68
+ ]
69
+ }