okstra 0.102.2 → 0.102.4
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.
- package/README.kr.md +5 -5
- package/README.md +5 -5
- package/docs/kr/architecture/storage-model.md +8 -3
- package/docs/kr/architecture.md +12 -13
- package/docs/kr/cli.md +4 -4
- package/docs/kr/container.md +1 -1
- package/docs/kr/performance-improvement-plan-v2.md +2 -2
- package/docs/project-structure-overview.md +2 -2
- package/package.json +7 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/lib/okstra/interactive.sh +12 -0
- package/runtime/bin/okstra-antigravity-exec.sh +1 -2
- package/runtime/bin/okstra-codex-exec.sh +1 -2
- package/runtime/bin/okstra-render-final-report.py +2 -9
- package/runtime/bin/okstra-spawn-followups.py +4 -6
- package/runtime/bin/okstra-trace-cleanup.sh +24 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +9 -11
- package/runtime/python/okstra_ctl/codex_dispatch.py +6 -13
- package/runtime/python/okstra_ctl/consumers.py +35 -0
- package/runtime/python/okstra_ctl/container.py +4 -6
- package/runtime/python/okstra_ctl/dispatch_core.py +8 -13
- package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
- package/runtime/python/okstra_ctl/handoff.py +2 -1
- package/runtime/python/okstra_ctl/implementation_outcome.py +271 -0
- package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
- package/runtime/python/okstra_ctl/path_hints.py +665 -0
- package/runtime/python/okstra_ctl/render.py +4 -3
- package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
- package/runtime/python/okstra_ctl/report_views.py +7 -4
- package/runtime/python/okstra_ctl/run.py +3 -10
- package/runtime/python/okstra_ctl/run_context.py +4 -2
- package/runtime/python/okstra_ctl/stage_targets.py +18 -0
- package/runtime/python/okstra_ctl/wizard.py +37 -26
- package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
- package/runtime/python/okstra_project/state.py +40 -2
- package/runtime/python/okstra_token_usage/report.py +2 -5
- package/runtime/validators/validate-report-views.py +2 -1
- package/runtime/validators/validate-run.py +1 -11
- package/src/commands/lifecycle/install.mjs +5 -5
- package/src/commands/lifecycle/uninstall.mjs +4 -4
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
- package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
- package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
- package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
- package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
- package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
- package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
- package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
- package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
- package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
- package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
- package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
- package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
- package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
- package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
- package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
- package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
- package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
- package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
- package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
- package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
- package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
- package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
- package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
- package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
- package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
- package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
- package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
- package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
- package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
- package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
- package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
- package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
- package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
- package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
- package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
- package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
- package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
- package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
- package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
- package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
- package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
- package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
- package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
- package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
- package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
- package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
- package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
- package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
- package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
- package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
- package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
- package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
- package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
- package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
- package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
- package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
- package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
- package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
- package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
- package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
- package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
- package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
- package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
- package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
- package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
- package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
- package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
- package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
- package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
- package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
- package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
- package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
- package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
- package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
- package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
- package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
- package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
- package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
- package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
- package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
- package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
- package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
- package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
- package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
- package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
- package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
- package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
- package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
- package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
- package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
- package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
- package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
- package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +0 -110
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# Forbidden-Actions SSOT Design (P2-2)
|
|
2
|
-
|
|
3
|
-
**Goal:** Eliminate the drift between the two places per-phase *forbidden actions* are authored, by making one JSON file the single source of truth (SSOT) from which both the launch-prompt boundary and the profile body derive.
|
|
4
|
-
|
|
5
|
-
**Scope:** `forbidden` only. `allowed`, required-workers, and profile `Purpose`/`Non-goals` are explicitly out of scope (user decision; see Non-goals).
|
|
6
|
-
|
|
7
|
-
**Format:** JSON data file (no YAML — the repo deliberately ships no YAML parser; `scripts/okstra_ctl/wizard.py:128` hand-parses frontmatter "no yaml dep", CLAUDE.md "stdlib first"). User-confirmed.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## 1. Current state (verified)
|
|
12
|
-
|
|
13
|
-
Per-phase forbidden actions are authored in **two** places and injected into the lead from **two** surfaces in a single run:
|
|
14
|
-
|
|
15
|
-
- **Python literal** — [`scripts/okstra_ctl/workflow.py:36`](../../../scripts/okstra_ctl/workflow.py) `PHASE_RULES[<phase>]["forbidden"]`, a `" - item\n"`-joined string. `compute_workflow_state` ([workflow.py:233](../../../scripts/okstra_ctl/workflow.py)) emits it as `PHASE_FORBIDDEN_ACTIONS`, injected into [`prompts/launch.template.md:16`](../../../prompts/launch.template.md) "Current Phase Boundary".
|
|
16
|
-
- **Profile prose** — `prompts/profiles/<phase>.md`. The full profile body is rendered into `analysis-profile.md` ([run.py:1812](../../../scripts/okstra_ctl/run.py)) and read by the lead.
|
|
17
|
-
|
|
18
|
-
Crucially, only **2 of 7** profiles carry a `Forbidden actions` block that duplicates `PHASE_RULES`:
|
|
19
|
-
|
|
20
|
-
| task type | profile forbidden block? | PHASE_RULES.forbidden? | duplicated? |
|
|
21
|
-
|---|---|---|---|
|
|
22
|
-
| requirements-discovery | no (`Non-goals` only, [requirements-discovery.md:57](../../../prompts/profiles/requirements-discovery.md)) | yes | no |
|
|
23
|
-
| improvement-discovery | no (`Non-goals` only) | yes | no |
|
|
24
|
-
| error-analysis | no (`Non-goals` only) | yes | no |
|
|
25
|
-
| implementation-planning | no (`Non-goals` only) | yes | no |
|
|
26
|
-
| **implementation** | **yes** ([implementation.md:34](../../../prompts/profiles/implementation.md), 8 items) | yes (13 items) | **yes, divergent** |
|
|
27
|
-
| final-verification | no (`Non-goals` only) | yes | no |
|
|
28
|
-
| **release-handoff** | **yes** ([release-handoff.md:62](../../../prompts/profiles/release-handoff.md), 15 items) | yes (11 items) | **yes, divergent** |
|
|
29
|
-
|
|
30
|
-
`Non-goals` is a *distinct concept* (scope guidance) from `Forbidden actions` (`contract-violated` safety rules). It is **not** consolidated by this work.
|
|
31
|
-
|
|
32
|
-
So the drift-prone duplication is exactly **implementation** and **release-handoff**, where the profile block and `PHASE_RULES` have diverged (different item counts, wording, ordering) with no test guarding them.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## 2. Design
|
|
37
|
-
|
|
38
|
-
### 2.1 SSOT file
|
|
39
|
-
|
|
40
|
-
Create `prompts/profiles/forbidden-actions.json`:
|
|
41
|
-
|
|
42
|
-
```json
|
|
43
|
-
{
|
|
44
|
-
"requirements-discovery": ["...", "..."],
|
|
45
|
-
"improvement-discovery": ["..."],
|
|
46
|
-
"error-analysis": ["..."],
|
|
47
|
-
"implementation-planning": ["..."],
|
|
48
|
-
"implementation": ["..."],
|
|
49
|
-
"final-verification": ["..."],
|
|
50
|
-
"release-handoff": ["..."],
|
|
51
|
-
"unknown": ["..."]
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
- Each value is the ordered list of forbidden-action items (one string per current ` - ` bullet, **without** the leading ` - `).
|
|
56
|
-
- `unknown` mirrors `PHASE_RULES_UNKNOWN["forbidden"]`.
|
|
57
|
-
- Lives under `prompts/profiles/` so it resolves the same way the profile `.md` files do (`workspace_root / "prompts" / "profiles" / ...`, [run.py:1073](../../../scripts/okstra_ctl/run.py)) and is synced into `runtime/` by `tools/build.mjs` (prompts/ is a build source).
|
|
58
|
-
|
|
59
|
-
### 2.2 Rendering & injection
|
|
60
|
-
|
|
61
|
-
In `workflow.py`:
|
|
62
|
-
|
|
63
|
-
- `def load_phase_forbidden(workspace_root: Path) -> dict[str, str]` — explicit IO: `json.load` the file, render each phase's list to the existing `" - item\n"` string shape via a pure helper `render_forbidden(items: list[str]) -> str`. Raise `FileNotFoundError`/`PrepareError`-style if missing (fail loud, no silent stale fallback).
|
|
64
|
-
- `compute_workflow_state(..., forbidden_by_phase: dict[str, str])` — new **required** keyword arg; `PHASE_FORBIDDEN_ACTIONS = forbidden_by_phase.get(task_type, forbidden_by_phase["unknown"])`.
|
|
65
|
-
- **Delete** the `"forbidden"` key from every `PHASE_RULES` entry and from `PHASE_RULES_UNKNOWN`. `PHASE_RULES` keeps `"allowed"` only.
|
|
66
|
-
|
|
67
|
-
In `run.py` (the only two `compute_workflow_state` call sites, [run.py:2012](../../../scripts/okstra_ctl/run.py), [run.py:2232](../../../scripts/okstra_ctl/run.py)):
|
|
68
|
-
|
|
69
|
-
- Load `forbidden_by_phase = workflow.load_phase_forbidden(workspace_root)` once (where `workspace_root` is already resolved for the profile file) and pass it to both calls.
|
|
70
|
-
- In `_write_instruction_set_sources` ([run.py:1798](../../../scripts/okstra_ctl/run.py)), add `{{PHASE_FORBIDDEN_ACTIONS}}` to the placeholder substitution applied to `profile_rendered`, using the rendered string for `inp.task_type`.
|
|
71
|
-
|
|
72
|
-
### 2.3 Profile body changes (only impl + release-handoff)
|
|
73
|
-
|
|
74
|
-
- [implementation.md:34-42](../../../prompts/profiles/implementation.md): keep the header line `- Forbidden actions — universal (any occurrence → terminal status \`contract-violated\`):`, replace the 8 hand-written bullets with the single token `{{PHASE_FORBIDDEN_ACTIONS}}`.
|
|
75
|
-
- [release-handoff.md:62-77](../../../prompts/profiles/release-handoff.md): keep the header line `- Forbidden actions (any occurrence → terminal status \`contract-violated\`):`, replace the 15 hand-written bullets (incl. the nested push-variant sub-list) with `{{PHASE_FORBIDDEN_ACTIONS}}`.
|
|
76
|
-
- The other 5 profiles are **untouched** (their `Non-goals` blocks stay; they have no forbidden duplication).
|
|
77
|
-
|
|
78
|
-
### 2.4 Canonical content (reconciliation)
|
|
79
|
-
|
|
80
|
-
For the 5 non-duplicated phases, the JSON list = current `PHASE_RULES.forbidden`, **verbatim** (launch injection byte-identical, nothing else references it).
|
|
81
|
-
|
|
82
|
-
For the 2 duplicated phases the JSON list = the **union** (lose no rule from either source):
|
|
83
|
-
|
|
84
|
-
- **implementation** = `PHASE_RULES["implementation"]["forbidden"]` (13 items, the near-superset) with the profile's `git push ... --dry-run` nuance folded into the publish/push item. Every profile item maps into a PHASE_RULES item (git push, publish, DB, infra, API-write, sub-agents, Edit/Write-before-gate, verifier-read-only).
|
|
85
|
-
- **release-handoff** = the profile's detailed 15-item list ([release-handoff.md:62-77](../../../prompts/profiles/release-handoff.md), the superset: explicit `--force`/`--force-with-lease`/`-f`/`+refspec`, base-branch push, `--no-verify`, `gh release create`/`edit` + `twine upload` + publish set, source edits, unselected command, weaker-flag retry, `TeamCreate`/`Agent` ban, unrecognised-reply). All `PHASE_RULES` release-handoff items are already covered by it.
|
|
86
|
-
|
|
87
|
-
### 2.5 Disclosed behavior changes
|
|
88
|
-
|
|
89
|
-
Nothing is lost; one surface becomes *more complete* for the 2 phases:
|
|
90
|
-
|
|
91
|
-
- **implementation**: profile body forbidden grows 8 → 13 items (now equals the launch boundary). Launch injection ≈ unchanged.
|
|
92
|
-
- **release-handoff**: launch boundary forbidden grows 11 → 15 items (now equals the detailed profile list). Profile body ≈ unchanged.
|
|
93
|
-
|
|
94
|
-
All other phases: byte-identical on both surfaces.
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## 3. Tests (TDD)
|
|
99
|
-
|
|
100
|
-
- `tests/test_phase_forbidden_ssot.py`:
|
|
101
|
-
- `load_phase_forbidden` covers every key in `PHASE_RULES` + `unknown` (no phase missing; no extra phase).
|
|
102
|
-
- `render_forbidden(["a","b"]) == " - a\n - b"` (exact shape match for injection).
|
|
103
|
-
- `compute_workflow_state(..., forbidden_by_phase=load_phase_forbidden(repo_root))` emits `PHASE_FORBIDDEN_ACTIONS` equal to the rendered JSON for a sample phase.
|
|
104
|
-
- `PHASE_RULES` no longer contains a `"forbidden"` key (regression guard against re-introducing the duplicate).
|
|
105
|
-
- `tests/test_profile_forbidden_placeholder.py` (or extend an existing profile/prompt-metadata test):
|
|
106
|
-
- `implementation.md` and `release-handoff.md` contain `{{PHASE_FORBIDDEN_ACTIONS}}` and **no** hand-written bullet under the `Forbidden actions` header.
|
|
107
|
-
- the 5 other profiles contain neither a `Forbidden actions` block nor the placeholder (untouched).
|
|
108
|
-
- Existing suite must stay green, especially `tests/test_workflow_phase_sequence.py` (update its `compute_workflow_state` calls to pass `forbidden_by_phase`), `validators/validate-workflow.sh`, and the launch-template render tests.
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## 4. Non-goals
|
|
113
|
-
|
|
114
|
-
- `allowed` outputs consolidation — left as-is, including the `release-handoff.md:54` "Allowed actions" block that also overlaps `PHASE_RULES` allowed. (Possible future P2-2b.)
|
|
115
|
-
- Required/optional workers, `Purpose`, `Non-goals` — untouched (different parsers, no forbidden drift).
|
|
116
|
-
- The 5 non-duplicated profiles' `Non-goals` prose — untouched.
|
|
117
|
-
- No new third-party dependency (JSON via stdlib).
|
|
118
|
-
- `runtime/` is never hand-edited; `npm run build` syncs the new JSON.
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## 5. File summary
|
|
123
|
-
|
|
124
|
-
| File | Change |
|
|
125
|
-
|---|---|
|
|
126
|
-
| `prompts/profiles/forbidden-actions.json` | **create** — the SSOT (7 phases + `unknown`) |
|
|
127
|
-
| `scripts/okstra_ctl/workflow.py` | add `render_forbidden`, `load_phase_forbidden`; `compute_workflow_state` takes `forbidden_by_phase`; delete `PHASE_RULES[*]["forbidden"]` |
|
|
128
|
-
| `scripts/okstra_ctl/run.py` | load `forbidden_by_phase`, pass to both `compute_workflow_state` calls, add `{{PHASE_FORBIDDEN_ACTIONS}}` to profile rendering |
|
|
129
|
-
| `prompts/profiles/implementation.md` | replace forbidden bullets with placeholder |
|
|
130
|
-
| `prompts/profiles/release-handoff.md` | replace forbidden bullets with placeholder |
|
|
131
|
-
| `tests/test_phase_forbidden_ssot.py` | **create** |
|
|
132
|
-
| `tests/test_profile_forbidden_placeholder.py` | **create** |
|
|
133
|
-
| `tests/test_workflow_phase_sequence.py` | update `compute_workflow_state` calls |
|
|
134
|
-
| `CHANGES.md`, plan doc | record |
|
|
@@ -1,284 +0,0 @@
|
|
|
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 / generic external CLI / …)에서든, 사용자가 모든 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)`(generic external 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
|
-
| pi / generic external CLI | `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 프롬프트를 만들지 않는다.
|