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
package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# implementation run-산출물 stage 격리 (parallel stage worktree race 해소) — 설계
|
|
2
|
-
|
|
3
|
-
## 배경 / 문제
|
|
4
|
-
|
|
5
|
-
[2026-06-06 stage worktree 격리 설계](2026-06-06-stage-worktree-isolation-design.md)는 `implementation`
|
|
6
|
-
의 **git 작업**을 stage 별 worktree(`.../<task-id>/stage-<N>/`, 브랜치 `<prefix>-<task-id>-s<N>`)와
|
|
7
|
-
flock registry 예약으로 격리했다. 그러나 **run 산출물/집계 레이어는 격리하지 않았다.**
|
|
8
|
-
|
|
9
|
-
`fontradar-v2-api:dev-9185` 실측: 사용자가 stage-1·stage-2 `implementation` run 을 같은 task-key 로
|
|
10
|
-
동시 실행 → git 커밋(`feat-dev-9185-s2`)은 브랜치로 안전히 분리됐지만, 두 run 이 같은
|
|
11
|
-
`runs/implementation/` 산출물 디렉터리를 공유해 stage-1 executor 가 자기 결과를 수동 백업해야 했다.
|
|
12
|
-
이것이 메모리에 기록된 **"parallel stage worktree race"** 해저드다.
|
|
13
|
-
|
|
14
|
-
## 검증된 현재 메커니즘 (코드 근거)
|
|
15
|
-
|
|
16
|
-
- `RUN_DIR = runs/<task-type>` = `runs/implementation` — 경로에 **stage 세그먼트 없음**
|
|
17
|
-
([paths.py:125](../../../scripts/okstra_ctl/paths.py:125)). 하위 `manifests/ state/ prompts/ reports/
|
|
18
|
-
status/ sessions/ logs/ worker-results/ carry/` 가 전부 task-type 단위로 공유된다.
|
|
19
|
-
- 파일은 seq-named(`-<task-type>-<seq>`)이라 파일명 충돌은 없지만, **seq 가 stage 를 인코딩하지
|
|
20
|
-
않는다** → `final-report-implementation-001.md`(stage2) / `-002.md`(stage1) 처럼 번호↔stage 매핑이
|
|
21
|
-
뒤섞인다.
|
|
22
|
-
- `lead-pane.id` 는 `<RUN_DIR>/state/lead-pane.id` 로 **seq 도 없는 단일 공유 파일**
|
|
23
|
-
([_common-contract.md:34](../../../prompts/profiles/_common-contract.md:34)) → 동시 run 이 서로 덮어써
|
|
24
|
-
pane cleanup 이 엉뚱한 pane 을 닫는다.
|
|
25
|
-
- `consumers.jsonl` 은 **공유 stage 원장**이고 run-root 당 append mutex 를 가진다
|
|
26
|
-
([consumers.py:1](../../../scripts/okstra_ctl/consumers.py:1), [run_context.py:112](../../../scripts/okstra_ctl/run_context.py:112)).
|
|
27
|
-
이것은 **공유가 정상**(stage 간 조율 SSOT)이다.
|
|
28
|
-
- stage 번호는 prepare 시점에 이미 resolve 된다
|
|
29
|
-
([run.py:387 `_resolve_effective_stages`](../../../scripts/okstra_ctl/run.py:387)) → RUN_DIR 계산에
|
|
30
|
-
주입 가능.
|
|
31
|
-
|
|
32
|
-
## 목표 / 비목표
|
|
33
|
-
|
|
34
|
-
### 목표
|
|
35
|
-
- `implementation` run 산출물을 **stage 별로 완전 격리**해, 독립 stage 동시 실행(이미 설계가 의도)이
|
|
36
|
-
산출물 레이어까지 안전하게 만든다.
|
|
37
|
-
- 2026-06-06 설계의 worktree 격리와 **대칭**되게 한다(worktree `stage-<N>/` ↔ run `stage-<N>/`).
|
|
38
|
-
|
|
39
|
-
### 비목표
|
|
40
|
-
- `implementation` 외 task-type(`requirements-discovery` / `error-analysis` /
|
|
41
|
-
`implementation-planning` / `final-verification` / `release-handoff` / `improvement-discovery`)의
|
|
42
|
-
run 경로는 **불변**(stage 개념 없음 → `runs/<task-type>/` 유지).
|
|
43
|
-
- `consumers.jsonl`·registry 를 stage 별로 쪼개지 **않는다** — 이들은 stage 간 조율 SSOT 로 공유 유지.
|
|
44
|
-
- 자동 fan-out·자동 머지 도입 없음(2026-06-06 비목표 계승).
|
|
45
|
-
|
|
46
|
-
## 설계 결정
|
|
47
|
-
|
|
48
|
-
| # | 결정 | 근거 |
|
|
49
|
-
|---|------|------|
|
|
50
|
-
| D1 | `implementation` 의 `RUN_DIR = runs/implementation/stage-<N>/` (stage 세그먼트 삽입). 그 외 task-type 은 `runs/<task-type>/` 그대로. | worktree `stage-<N>/` 와 대칭. stage 는 prepare 시 resolve 됨. |
|
|
51
|
-
| D2 | seq 는 **stage 디렉터리 내부**에서 유지 — `runs/implementation/stage-<N>/reports/final-report-implementation-<seq>.md`. 한 stage 의 재실행(rerun)을 seq 로 구분. | stage 1개 ↔ run 1개지만 rerun 존재. |
|
|
52
|
-
| D3 | `consumers.jsonl` 은 stage 위(부모) `runs/implementation/consumers.jsonl` 에 **공유 유지**. mutex 도 그 위치. | 공유 stage 원장. stage 별로 쪼개면 ready-set 계산 불가. |
|
|
53
|
-
| D4 | `lead-pane.id` 는 stage-격리된 `<RUN_DIR>/state/lead-pane.id` 로 자동 이동(D1 의 부수효과) → 동시 run pane cross-talk 제거. | 단일 공유 파일 문제 해소. |
|
|
54
|
-
| D5 | 비-git/degradation 경로(`skipped-not-git` 등)는 stage 격리 미적용 시 기존 `runs/implementation/` 로 fallback(2026-06-06 degradation 신호 계승). | render-only/non-git 테스트 보존. |
|
|
55
|
-
|
|
56
|
-
## 구현 범위 (개요 — 정확한 sweep 은 plan 에서)
|
|
57
|
-
|
|
58
|
-
1. **`paths.py` `compute_run_paths`** — `task_type == implementation` 이고 stage 가 주어지면
|
|
59
|
-
`run_dir = runs_dir / "implementation" / f"stage-{N}"`. stage 미지정(비-impl)이면 현행 그대로.
|
|
60
|
-
stage 파라미터 추가(부수효과 없음 유지).
|
|
61
|
-
2. **`run.py`** — resolve 한 effective stage 를 `compute_run_paths` 에 전달. `consumers.jsonl` 경로는
|
|
62
|
-
stage 위(부모)로 고정(현행이 run-root 기준이면 위치 보존 확인).
|
|
63
|
-
3. **읽기/집계 측 sweep** — `render.py`, `render_final_report.py`, `report_views.py`,
|
|
64
|
-
`context_cost.py`, `index.py`, `reconcile.py`, `backfill.py`, `workflow.py`, `locks.py` 가
|
|
65
|
-
`runs/<task-type>/...` 를 가정하는 부분을 stage-aware 로. (대부분 ctx 의 `RUN_DIR`/path dict 를
|
|
66
|
-
쓰면 자동 흡수되나, hard-coded `runs/implementation` 문자열은 개별 확인.)
|
|
67
|
-
4. **validators** — `validate-run.py`, `validate_fanout.py`, `validate-workflow.sh`,
|
|
68
|
-
`validators/lib/fixtures.sh` 의 run-경로 가정 갱신.
|
|
69
|
-
5. **contract/skill** — `_common-contract.md` 의 `<RUN_DIR>` placeholder 는 치환이라 무변경 가능성이
|
|
70
|
-
크나, `runs/<task-type>/` 를 직접 적은 문구(report-writer dispatch 경로 등) 확인.
|
|
71
|
-
6. **테스트** — 동시 stage-1/stage-2 run 이 서로 다른 `stage-<N>/` 산출물 트리를 쓰고 `lead-pane.id`
|
|
72
|
-
가 분리되며 `consumers.jsonl` 은 공유됨을 e2e 로 가드. 비-impl task-type 경로 불변 회귀.
|
|
73
|
-
|
|
74
|
-
## 마이그레이션 / 호환
|
|
75
|
-
|
|
76
|
-
- **pre-1.0 무호환 정책**: 기존 `runs/implementation/` 루트에 있던 산출물은 새 reader 가 stage 경로에서
|
|
77
|
-
찾으므로 **이전 위치 보고서는 자동 인식 안 됨**. in-flight task(dev-9185 등)는 수동 정리 또는 재실행
|
|
78
|
-
대상으로 둔다(읽기 fallback 도입하지 않음 — 무호환 정책).
|
|
79
|
-
- 사용자 영향(CHANGES.md): "implementation 산출물이 `runs/implementation/stage-<N>/` 로 stage 별
|
|
80
|
-
격리돼, 같은 task 의 여러 stage 를 동시 실행해도 보고서·state 가 섞이지 않는다."
|
|
81
|
-
|
|
82
|
-
## enforcement 정직성 / 한계
|
|
83
|
-
|
|
84
|
-
- 동시성의 강제는 여전히 **registry flock 예약 + consumers ledger**다(2026-06-06 SSOT). 본 설계는
|
|
85
|
-
그 위에서 **산출물 격리**만 더해 이미 지원되는 동시성을 안전하게 만든다.
|
|
86
|
-
- `consumers.jsonl`·registry 는 의도적으로 공유 — 완전 무공유가 아니다. 격리되는 것은 per-run
|
|
87
|
-
출력(reports/state/worker-results 등)이다.
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# BLOCKING 계약 3종 post-hoc conformance 검사 설계
|
|
2
|
-
|
|
3
|
-
- 작성일: 2026-06-10
|
|
4
|
-
- 상태: 구현 완료 (2026-06-10 승인 — `validators/validate_session_conformance.py` 로 구현, 실 run 검증 포함)
|
|
5
|
-
- 관련 글로벌 규칙: CLAUDE.md "선언과 강제를 구분하라" (규칙 #3) — 문서에 MUST/BLOCKING 을 쓸 때마다 "어디서 어떻게 검증되는가" 한 줄이 있어야 한다.
|
|
6
|
-
|
|
7
|
-
## 1. 배경과 동기
|
|
8
|
-
|
|
9
|
-
`agents/SKILL.md` 는 아래 3개 계약을 BLOCKING 으로 선언하지만, 현재 어떤 코드 경로(validator / 테스트 / 런타임 가드)도 위반을 실패로 만들지 않는다. 선언만 있고 강제가 없는 상태다.
|
|
10
|
-
|
|
11
|
-
| # | 계약 | 선언 위치 | 현재 강제 |
|
|
12
|
-
|---|------|----------|----------|
|
|
13
|
-
| 1 | lead 의 `PROGRESS: <phase-id>` 체크포인트 라인 12종 | [agents/SKILL.md:83-105](../../../agents/SKILL.md:83) | 없음 — `grep -rln "PROGRESS:" validators/ tests/ scripts/okstra_ctl/` → 0 hit (2026-06-10 확인) |
|
|
14
|
-
| 2 | claude-worker 의 audit 사이드카 5분 heartbeat (`- PROGRESS: <stage> <ISO-UTC>` append) | [agents/SKILL.md:384](../../../agents/SKILL.md:384), [agents/workers/claude-worker.md:65](../../../agents/workers/claude-worker.md:65) | 사이드카 **존재** 만 검사 ([validators/validate-run.py:974-981](../../../validators/validate-run.py:974)). cadence 는 미검사. 기존 heartbeat 테스트([tests/test_okstra_wrapper_status.py](../../../tests/test_okstra_wrapper_status.py))는 codex/gemini wrapper 의 `.status.json` 만 다룬다 |
|
|
15
|
-
| 3 | implementation sidecar entry guard — Phase 5/6 진입 전 `_implementation-executor/-verifier/-deliverable.md` Read 의무 | [agents/SKILL.md:176-188](../../../agents/SKILL.md:176) | 없음 — "lead session jsonl 에서 확인 가능" 이라고 선언만 하고 아무도 jsonl 을 보지 않는다 |
|
|
16
|
-
|
|
17
|
-
세 계약 모두 **lead 세션 jsonl 또는 run 산출물(디스크 파일)에 사후 증거가 남는다**. 따라서 Phase 7 에서 lead 가 반드시 실행하는 `validators/validate-run.py` 에 post-hoc 검사를 추가하는 것이 가장 자연스러운 강제 지점이다 — 위반 시 기존 메커니즘 그대로 `validation failed → run status = contract-violated` 로 떨어진다 ([validators/validate-run.py:256-258](../../../validators/validate-run.py:256)).
|
|
18
|
-
|
|
19
|
-
## 2. 범위 / 비범위
|
|
20
|
-
|
|
21
|
-
**범위**
|
|
22
|
-
- `validate-run.py` 실행 시점(Phase 7)에 검증 가능한 post-hoc 검사 3종.
|
|
23
|
-
- 검사 로직은 새 모듈 1개로 분리, `validate-run.py` `main()` 에서 호출.
|
|
24
|
-
- 단위 테스트 (합성 jsonl / 사이드카 fixture).
|
|
25
|
-
|
|
26
|
-
**비범위**
|
|
27
|
-
- 런타임(실시간) 강제 — 예: 5분 stale mtime 감시는 run 진행 중 lead 의 폴링 영역이며 post-hoc 으로 대체 불가. 이 설계는 "위반한 run 이 통과 판정을 받는 것"을 막는다.
|
|
28
|
-
- 악의적 위조 방어 — heartbeat timestamp 는 worker 자기 보고 값이다. post-hoc 검사는 **누락**을 잡지, 위조를 잡지 않는다.
|
|
29
|
-
- `PROGRESS: complete` / `phase-7-teardown` 라인 검사 — validator 실행 시점 **이후**에 출력되는 라인이므로 구조적으로 검사 불가 (아래 4.1 참고).
|
|
30
|
-
|
|
31
|
-
## 3. 설계 개요
|
|
32
|
-
|
|
33
|
-
### 3.1 모듈 배치
|
|
34
|
-
|
|
35
|
-
새 모듈: **`validators/validate_session_conformance.py`**
|
|
36
|
-
|
|
37
|
-
- 기존 phase-특화 validator 와 같은 패턴 — [validators/validate_fanout.py](../../../validators/validate_fanout.py), [validators/validate_improvement_report.py](../../../validators/validate_improvement_report.py) 처럼 `validate-run.py` 가 import 해 결과를 `failures` 리스트로 fold 한다 (접두 `session-conformance: `).
|
|
38
|
-
- 기존 `scripts/okstra_ctl/conformance.py` 는 **stage QA conformance** (별개 개념, [docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md](2026-06-07-stage-conformance-qa-design.md)) 이므로 이름 충돌을 피해 `session_conformance` 로 명명한다.
|
|
39
|
-
|
|
40
|
-
### 3.2 공유 인프라 — 단일 참조점 재사용
|
|
41
|
-
|
|
42
|
-
lead 세션 jsonl 탐색·파싱은 이미 `okstra_token_usage` 패키지에 구현돼 있다. 중복 구현하지 않고 그대로 쓴다.
|
|
43
|
-
|
|
44
|
-
| 기능 | 재사용 지점 |
|
|
45
|
-
|------|------------|
|
|
46
|
-
| 프로젝트별 jsonl 디렉터리 (`~/.claude/projects/<encoded-cwd>/`) | [scripts/okstra_token_usage/paths.py:19](../../../scripts/okstra_token_usage/paths.py:19) `claude_project_dir` |
|
|
47
|
-
| lead sessionId → jsonl 매핑 (+ teamName 태그 스캔 폴백) | [scripts/okstra_token_usage/claude.py:100](../../../scripts/okstra_token_usage/claude.py:100) `find_claude_team_sessions` |
|
|
48
|
-
| jsonl 레코드 iterator | `scripts/okstra_token_usage/jsonl_io.py` `iter_jsonl` |
|
|
49
|
-
| run 시간 윈도우 (in-session lead 의 세션 전체 jsonl 에서 이번 run 만 스코핑) | [scripts/okstra_token_usage/collect.py:121-137](../../../scripts/okstra_token_usage/collect.py:121) `_resolve_run_window` — **public `resolve_run_window` 로 승격** 하고 `collect()` 와 새 모듈이 함께 사용 (pre-1.0 이므로 호환 shim 없이 rename) |
|
|
50
|
-
| lead sessionId 출처 | team-state `lead.sessionId` ([scripts/okstra_ctl/render.py:425](../../../scripts/okstra_ctl/render.py:425) 에서 기록, [scripts/okstra_token_usage/collect.py:165](../../../scripts/okstra_token_usage/collect.py:165) 에서 소비) |
|
|
51
|
-
|
|
52
|
-
run 윈도우 스코핑이 필수인 이유: in-session(`okstra-run` skill) lead 는 사용자 세션 전체 jsonl 에 기록되므로, 윈도우 없이 스캔하면 **같은 세션의 이전 okstra run 이 남긴 PROGRESS 라인이 이번 run 의 증거로 오인**된다(false pass). 토큰 집계가 이미 같은 이유로 윈도우를 쓴다 ([collect.py:124-130](../../../scripts/okstra_token_usage/collect.py:124) 주석).
|
|
53
|
-
|
|
54
|
-
### 3.3 jsonl 스캔 규칙 (검사 1·3 공통)
|
|
55
|
-
|
|
56
|
-
- 스캔 대상 레코드: `type == "assistant"` 인 레코드만. — Skill 호출 시 SKILL.md 본문(체크포인트 라인 예시 포함!)이 tool_result(user 레코드)로 transcript 에 주입되므로, assistant 외 레코드를 보면 즉시 false pass 가 난다.
|
|
57
|
-
- PROGRESS 라인: assistant 레코드의 `message.content[].type == "text"` 블록에서 line-anchored 정규식 `^PROGRESS: <phase-id>\b` (MULTILINE) 으로 추출. thinking 블록은 제외.
|
|
58
|
-
- Read tool-call: assistant 레코드의 `message.content[].type == "tool_use"`, `name == "Read"`, `input.file_path` 의 basename 매칭.
|
|
59
|
-
- 각 증거에 레코드 `timestamp` 를 부착해 run 윈도우(`since ≤ ts ≤ until`) 안의 것만 인정.
|
|
60
|
-
- 스캔 대상 파일: `find_claude_team_sessions(cwd, team_name, lead_sid)` 결과 중 **lead 후보 세트** = {기록된 `lead.sessionId` jsonl} ∪ {team 태그는 있으나 `agentName` 이 없는 jsonl}. 후자는 `claude --resume` 으로 lead 세션이 fork 된 경우(새 sessionId, agentName 없음)를 흡수한다 — worker 세션은 `agentName` 이 있으므로 자연 배제된다.
|
|
61
|
-
|
|
62
|
-
**P0 검증 항목 (구현 첫 단계):** 위 레코드 형태 가정(assistant text 블록 / `tool_use.name=="Read"` / `input.file_path`)을 실제 okstra run 의 lead jsonl 1개로 확인한 뒤 파서를 확정한다. 가정이 틀리면 설계로 되돌아온다.
|
|
63
|
-
|
|
64
|
-
## 4. 검사 상세
|
|
65
|
-
|
|
66
|
-
### 4.1 검사 1 — PROGRESS 체크포인트 라인
|
|
67
|
-
|
|
68
|
-
[agents/SKILL.md:87-101](../../../agents/SKILL.md:87) 의 12종 중 **validator 실행 시점에 이미 출력돼 있어야 하는 것만** 필수로 요구한다. run 형태(roster, 산출물)에 따라 요구 세트를 동적으로 구성한다:
|
|
69
|
-
|
|
70
|
-
| 체크포인트 | 요구 조건 | 판정 |
|
|
71
|
-
|-----------|----------|------|
|
|
72
|
-
| `phase-1-intake reading…` / `phase-1-intake complete` | 항상 | 각 ≥1 |
|
|
73
|
-
| `phase-2-prompts…` | 항상 | ≥1 |
|
|
74
|
-
| `phase-3-team-create…` | worker 가 1개 이상 dispatch 된 경우 (team-state worker status ∈ {completed, timeout, error, in-progress} — [validators/validate-run.py:373-377](../../../validators/validate-run.py:373) 의 `any_dispatched` 와 동일 기준) | ≥1 |
|
|
75
|
-
| `phase-4-dispatch worker=<role>…` | dispatch 시도된(status ∈ ATTEMPTED_STATUSES) worker 마다 | role 별 ≥1. role 매칭은 normalize(소문자화, 공백/하이픈 동일시) 후 `worker=` 토큰 비교; normalize 매칭 실패 시 해당 worker 를 실패 항목으로 보고 |
|
|
76
|
-
| `phase-5-poll…` | 검사 안 함 (pending 집합이 관측되지 않은 짧은 run 에선 합법적으로 0개) | — |
|
|
77
|
-
| `phase-5-collect worker=<role>…` | status == completed 인 worker 마다 | role 별 ≥1 |
|
|
78
|
-
| `phase-5.5-convergence round=…` | convergence state artifact 가 run 디렉터리에 존재하는 경우 | ≥1 |
|
|
79
|
-
| `phase-5.6-critic…` | 검사 안 함 (opt-in — [agents/SKILL.md:97](../../../agents/SKILL.md:97)) | — |
|
|
80
|
-
| `phase-6-synthesis…` | `Report writer worker` 가 required roster 에 있는 경우 | ≥1 |
|
|
81
|
-
| `phase-7-persist…` | 항상 (validator 호출은 Phase 7 내부이므로 시작 라인은 이미 출력됨) | ≥1 |
|
|
82
|
-
| `phase-7-teardown…` / `complete…` | 검사 안 함 (validator 실행 **이후** 출력) | — |
|
|
83
|
-
|
|
84
|
-
실패 메시지는 누락된 체크포인트 id 와 SKILL.md 의 해당 행을 명시한다.
|
|
85
|
-
|
|
86
|
-
**lead jsonl 미발견 시:** 실패로 처리한다. 근거 — 토큰 사용량 계약이 이미 같은 원칙을 강제한다: lead jsonl 을 못 찾으면 `accuracy-failed` 로 validation 이 실패하므로 ([validators/validate-run.py:1819-1824](../../../validators/validate-run.py:1819)), "jsonl 이 없어 conformance 를 못 본다" 는 상황은 어차피 통과할 수 없는 run 이다. 별도 opt-out 은 만들지 않는다 (allowlist 원칙 — 통과 조건만 정의).
|
|
87
|
-
|
|
88
|
-
### 4.2 검사 2 — claude-worker heartbeat cadence
|
|
89
|
-
|
|
90
|
-
대상: `worker-results/claude-worker-audit-<task-type>-<seq>.md` (jsonl 불필요 — 디스크 파일 파싱). 사이드카 **존재** 는 기존 검사가 이미 강제하므로 ([validators/validate-run.py:974-981](../../../validators/validate-run.py:974)), 이 검사는 **내용 cadence** 만 추가한다. 대상 worker 는 claude-worker 가 ATTEMPTED 인 run 으로 한정한다 — 5분 heartbeat 계약은 [claude-worker.md:65](../../../agents/workers/claude-worker.md:65) 의 것이고, codex/gemini 는 `.status.json` watchdog 가 별도 경로로 이미 강제된다.
|
|
91
|
-
|
|
92
|
-
파싱: `- PROGRESS: <stage> <ISO-8601-UTC>` 라인 목록을 추출. 검사 항목:
|
|
93
|
-
|
|
94
|
-
1. **시작 마커**: 첫 PROGRESS 라인의 stage 가 `started` 일 것 ("write the sidecar … with one `- PROGRESS: started <ISO>` line").
|
|
95
|
-
2. **종료 직전 마커**: `write-result-start` stage 라인이 존재할 것 (result 파일이 있는데 이 마커가 없으면 단계별 append 계약 위반).
|
|
96
|
-
3. **timestamp 파싱 가능 + 단조 비감소**: 파싱 불가 라인, 역행 timestamp 는 각각 실패 항목.
|
|
97
|
-
4. **cadence**: 연속 PROGRESS 라인 간 간격 ≤ **5분 + grace 60초**. 계약상 5분이지만 worker 가 append 직전 측정한 시각과 실제 쓰기 사이 지연을 흡수하기 위한 고정 grace 다.
|
|
98
|
-
|
|
99
|
-
**한계 (명시):** 마지막 PROGRESS 라인 **이후** 의 hang 은 post-hoc 으로 잡을 수 없다 (종료 시각 anchor 가 없다 — 파일 mtime 은 git/조작에 취약해 anchor 로 쓰지 않는다). 그 구간은 run 중 lead 의 5-min stale mtime 감시([agents/SKILL.md:384](../../../agents/SKILL.md:384))가 담당하는 런타임 영역이다.
|
|
100
|
-
|
|
101
|
-
### 4.3 검사 3 — implementation sidecar entry guard
|
|
102
|
-
|
|
103
|
-
`task_type == "implementation"` 인 run 에만 적용. lead jsonl(run 윈도우 내)에서 `Read` tool_use 의 `input.file_path` basename 으로 다음을 요구한다:
|
|
104
|
-
|
|
105
|
-
1. **존재성**: `_implementation-executor.md`, `_implementation-verifier.md`, `_implementation-deliverable.md` 각각에 대한 Read ≥1.
|
|
106
|
-
2. **순서** (anchor 가 있을 때만): 검사 1 이 수집한 PROGRESS 라인을 anchor 로 사용 —
|
|
107
|
-
- executor·verifier sidecar Read 의 timestamp < 첫 `PROGRESS: phase-6-synthesis` 라인 timestamp ([agents/SKILL.md:182-183](../../../agents/SKILL.md:182): 둘 다 "Read at Phase 5").
|
|
108
|
-
- deliverable sidecar Read 의 timestamp < 첫 `PROGRESS: phase-7-persist` 라인 timestamp ([agents/SKILL.md:184](../../../agents/SKILL.md:184): "Read at Phase 6").
|
|
109
|
-
- anchor 라인 자체가 없으면(검사 1 이 이미 그 누락을 실패로 보고) 순서 검사는 생략하고 존재성만 본다 — 같은 원인으로 이중 실패를 쌓지 않는다.
|
|
110
|
-
3. **fresh-read 규칙** ([agents/SKILL.md:188](../../../agents/SKILL.md:188) "이전 run 기억으로 갈음 불가"): run 윈도우 스코핑 자체가 이를 보장한다 — 이번 run 윈도우 안의 Read 만 인정된다.
|
|
111
|
-
|
|
112
|
-
basename 매칭인 이유: sidecar 의 절대 경로는 레이어(repo / `~/.okstra/lib` / 설치본)에 따라 달라지지만 파일명은 세 레이어에서 동일하며, 언더스코어 접두 파일명은 이 3종 외에 lead 가 Read 할 일이 없을 만큼 특이적이다.
|
|
113
|
-
|
|
114
|
-
## 5. validate-run.py 통합
|
|
115
|
-
|
|
116
|
-
```
|
|
117
|
-
main() # validators/validate-run.py:1969
|
|
118
|
-
...
|
|
119
|
-
task_type = effective_run_task_type(...)
|
|
120
|
-
+ sc_result = validate_session_conformance(
|
|
121
|
-
+ team_state=team_state,
|
|
122
|
-
+ project_root=project_root,
|
|
123
|
-
+ report_path=report_path,
|
|
124
|
-
+ run_manifest=run_manifest,
|
|
125
|
-
+ task_type=task_type,
|
|
126
|
-
+ claude_projects_dir=args.claude_projects_dir, # 기본 None → 실제 ~/.claude/projects
|
|
127
|
-
+ )
|
|
128
|
-
+ failures.extend(f"session-conformance: {e}" for e in sc_result.errors)
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
- 새 CLI 인자 `--claude-projects-dir <path>` (optional): 테스트·진단용 주입 시드. 기본값은 실제 디렉터리. env var 가 아닌 명시적 인자로 둔다 (사용자 규칙: env var 보다 명시적 인자). launch prompt 가 렌더링하는 validator 호출 커맨드는 변경 불필요 (기본값 사용).
|
|
132
|
-
- 검사 2 (heartbeat) 는 jsonl 과 무관하므로 jsonl 미발견 시에도 항상 수행.
|
|
133
|
-
- 실패 시 동작은 기존과 동일: `failures` 비어 있지 않음 → `validation_status = "failed"` → run/task manifest 에 `contract-violated` 기록 ([validators/validate-run.py:2069-2076](../../../validators/validate-run.py:2069)).
|
|
134
|
-
|
|
135
|
-
## 6. 테스트 전략
|
|
136
|
-
|
|
137
|
-
새 파일 `tests/test_validate_session_conformance.py` — 기존 validate-run 테스트 패턴(importlib 으로 모듈 로드 후 함수 직접 호출, [tests/test_validate_run_report_format.py:33-38](../../../tests/test_validate_run_report_format.py:33))을 따른다. `main()` 통합 경로가 아닌 함수 단위로 검증하므로 기존 validate-run 테스트는 영향받지 않는다.
|
|
138
|
-
|
|
139
|
-
| 케이스 | fixture |
|
|
140
|
-
|--------|---------|
|
|
141
|
-
| 검사 1 happy path / 체크포인트 누락 / 윈도우 밖 라인 무시(직전 run 오염) / SKILL.md 본문 주입(false-pass 방지: user 레코드의 PROGRESS 텍스트는 불인정) | 합성 jsonl (tmp_path 에 작성, `--claude-projects-dir` 주입 시드 사용) |
|
|
142
|
-
| 검사 2 happy path / started 누락 / write-result-start 누락 / 6분 gap / timestamp 역행·파싱 불가 | 합성 audit 사이드카 md |
|
|
143
|
-
| 검사 3 3종 Read 존재 / 1종 누락 / phase-6 anchor 이후 Read(순서 위반) / anchor 부재 시 존재성만 | 합성 jsonl |
|
|
144
|
-
| lead jsonl 미발견 → 실패 | 빈 projects dir |
|
|
145
|
-
| `resolve_run_window` 승격 후 collect 회귀 없음 | 기존 token-usage 테스트 통과 확인 |
|
|
146
|
-
|
|
147
|
-
**구현 완료 판정의 실행 검증 (사용자 규칙 — mocked green 으로 "검증됨" 선언 금지):** 단위 테스트 외에, 실제 okstra run 1회의 실 artifacts (lead jsonl + audit 사이드카) 에 대해 validate-run.py 를 실행해 관측한 결과를 보고한다. 그 전까지 상태 표기는 "정적/단위테스트상 통과, 실 run 미검증".
|
|
148
|
-
|
|
149
|
-
## 7. 리스크와 한계
|
|
150
|
-
|
|
151
|
-
1. **jsonl flush 타이밍**: validator 는 lead 의 Bash tool-call 로 실행된다. 그 시점까지의 assistant 메시지가 jsonl 에 기록돼 있다는 가정 — Claude Code 는 메시지 단위로 append 하므로 성립하지만, P0 에서 실 jsonl 로 함께 확인한다.
|
|
152
|
-
2. **harness 포맷 의존**: jsonl 레코드 스키마는 Claude Code 내부 포맷이다. 토큰 집계(`okstra_token_usage`)가 이미 같은 의존을 지므로 새 위험은 아니지만, 포맷 변경 시 두 소비자가 함께 깨진다 — 파서는 "인식 불가 레코드는 건너뜀 + 증거 0건이면 실패" 로 보수적으로 동작해 silent pass 는 없다.
|
|
153
|
-
3. **소급 실패**: 이 검사 추가 후 과거 계약을 안 지킨 lead 의 run 은 Phase 7 에서 실패하게 된다 — 이것이 의도다. validate-run 은 새 run 의 Phase 7 에서만 호출되므로 이미 완료된 과거 run 에는 영향 없다.
|
|
154
|
-
4. **자기 보고 timestamp**: 검사 2 의 한계 (§4.2). 누락 탐지가 목표이고, 위조 방어는 비범위.
|
|
155
|
-
5. **role 표기 편차**: 검사 1 의 `worker=<role>` 매칭은 normalize 로 흡수하되, 실 run 에서 편차가 관측되면 매칭 규칙을 SSOT(team-state role 문자열) 기준으로 조정한다.
|
|
156
|
-
|
|
157
|
-
## 8. 구현 단계 (승인 후)
|
|
158
|
-
|
|
159
|
-
1. **P0**: 실 lead jsonl 1개로 §3.3 레코드 가정 검증 → 파서 시그니처 확정.
|
|
160
|
-
2. `scripts/okstra_token_usage/collect.py` — `_resolve_run_window` → `resolve_run_window` 승격 (호출자 갱신).
|
|
161
|
-
3. `validators/validate_session_conformance.py` 신규 — 검사 1·2·3 + Result 타입 (`ok` / `errors`), 기존 validator 모듈 패턴 준수.
|
|
162
|
-
4. `validators/validate-run.py` — `--claude-projects-dir` 인자 + `main()` 통합 (§5).
|
|
163
|
-
5. `tests/test_validate_session_conformance.py` 신규 (§6 케이스).
|
|
164
|
-
6. 선언부 갱신 — 강제 지점 명시 한 줄씩 추가 (글로벌 규칙 #3 의 "옆에 한 줄"):
|
|
165
|
-
- [agents/SKILL.md:83](../../../agents/SKILL.md:83) Progress reporting 절에 "Phase 7 `validate-run.py` 가 lead 세션 jsonl 을 스캔해 누락 시 contract-violated 처리" 1줄.
|
|
166
|
-
- [agents/workers/claude-worker.md:65](../../../agents/workers/claude-worker.md:65) Heartbeat 절에 cadence post-hoc 검사 1줄.
|
|
167
|
-
- [agents/SKILL.md:186](../../../agents/SKILL.md:186) Entry guard 절의 "visible in the lead session jsonl" 뒤에 validator 가 실제로 검사함을 명시.
|
|
168
|
-
7. `npm run build` + `python3 -m pytest tests/` + 실 run 1회 검증 (§6) → `CHANGES.md` 에 `사용자 영향:` 라인 포함 엔트리 추가.
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# 동시-run team 가드: 후발 run no-team background 회피 설계
|
|
2
|
-
|
|
3
|
-
- 작성일: 2026-06-10
|
|
4
|
-
- 상태: 설계 승인됨 (구현 전, 실측 게이트 미통과)
|
|
5
|
-
- 범위: 같은 task-key 의 implementation run 이 동시에 돌 때 발생하는 `~/.claude/teams/` team config race 의 **예방**. 이미 발생한 race 의 복구는 범위 밖(§6).
|
|
6
|
-
|
|
7
|
-
## 1. 문제 (런타임으로 관찰)
|
|
8
|
-
|
|
9
|
-
사용자가 같은 task-key(`fontradar-v2-api:calcule-des-prix-1-1:dev-9185`)에 대해 두 Claude Code 세션을 동시에 돌렸다. 세션 A 는 implementation stage-4 의 Claude verifier 를 dispatch 하려 했고, 세션 B 는 stage-5 를 동시에 실행 중이었다. 결과:
|
|
10
|
-
|
|
11
|
-
| 관찰 | 내용 |
|
|
12
|
-
|---|---|
|
|
13
|
-
| dispatch 실패 | stage-4 verifier 의 `Agent(... team_name ...)` 가 `Team config file unreadable (lock acquired, read failed)` 로 거부 |
|
|
14
|
-
| team config 소실 | stage-4 team 의 `~/.claude/teams/<team>/config.json` 이 사라짐 |
|
|
15
|
-
| 다른 team 정상 | `okstra-…-dev-9185-s5` team 은 정상 존재 (config.json 15K, 20:27 생성) |
|
|
16
|
-
| 코드는 무사 | stage-4 의 코드/worktree 는 intact, real-DB PASS 완료 — **데이터 손실 없음, dispatch 만 막힘** |
|
|
17
|
-
|
|
18
|
-
즉 stage-4·stage-5 는 코드상 독립(둘 다 deps 1,2,6)이라 worktree/코드 충돌은 없었고, **공유 상태(`~/.claude/teams/` team config)에서만 race** 가 났다.
|
|
19
|
-
|
|
20
|
-
## 2. 근본 원인 (코드로 확정)
|
|
21
|
-
|
|
22
|
-
1. **team 생성/삭제는 lead AI 의 하네스 도구 호출이라 okstra 의 flock 밖이다.** `prepare_task_bundle` 은 launch prompt 에 team 이름과 "Team Creation Gate (BLOCKING)" 블록만 렌더하고([scripts/okstra_ctl/render.py:1665](../../../scripts/okstra_ctl/render.py)) 즉시 반환한다. 실제 `TeamCreate`/`TeamDelete` 는 lead 가 Phase 3·7 에서 직접 호출한다([agents/SKILL.md:212](../../../agents/SKILL.md), [prompts/profiles/_common-contract.md:60](../../../prompts/profiles/_common-contract.md)). lead 가 team 을 들고 도는 수십 분간 okstra 의 Python 프로세스는 이미 종료돼 있어, TeamCreate~TeamDelete 임계구역을 flock 으로 감쌀 수단이 없다.
|
|
23
|
-
|
|
24
|
-
2. **worktree/registry 레이어는 stage-key 까지 격리되지만 team 레이어는 그 격리 밖이다.** worktree 프로비저닝은 task-key flock([scripts/okstra_ctl/locks.py:30](../../../scripts/okstra_ctl/locks.py) `worktree_provision_mutex`)으로, stage 예약은 registry flock([scripts/okstra_ctl/worktree_registry.py:264](../../../scripts/okstra_ctl/worktree_registry.py) `list_active_stage_numbers`)으로 직렬화된다. team_name 도 stage 별로 분리된다(`okstra-<task-key>-s<N>`, [scripts/okstra_ctl/render.py:1659](../../../scripts/okstra_ctl/render.py)). 그러나 이 어떤 okstra flock 도 team 생성/삭제를 감싸지 않는다.
|
|
25
|
-
|
|
26
|
-
3. **`Team config file unreadable (lock acquired, read failed)` 문자열은 okstra 레포 어디에도 없다**(grep 전수 확인). 이는 Claude Code 하네스의 team config I/O 계층이 내는 메시지이며, team config 파일의 atomicity 는 okstra 가 통제할 수 없다([scripts/okstra_ctl/team_reconcile.py:17](../../../scripts/okstra_ctl/team_reconcile.py) 주석: "harness behaviour that can only be confirmed inside a real run").
|
|
27
|
-
|
|
28
|
-
종합: 두 동시 run 이 같은 `~/.claude/teams/` 부모를 동시에 건드린 것이 race 의 원천이다. team 이름이 stage 별로 달라도(파일 경로는 안 겹쳐도) 하네스의 부모-디렉토리 단위 동작이 한쪽 team config 를 관측 불가능하게 만들 수 있다.
|
|
29
|
-
|
|
30
|
-
## 3. 검토한 대안과 채택 근거
|
|
31
|
-
|
|
32
|
-
| 방향 | stage 병렬 보존 | enforce 지점 | race 회피 | 기각/채택 |
|
|
33
|
-
|---|---|---|---|---|
|
|
34
|
-
| A. lead-driven advisory lock (TeamCreate~TeamDelete 구간 직렬화) | ✅ | lead 프롬프트(협조 의존) | △ 부분 | 기각 |
|
|
35
|
-
| B. 같은 task-key 동시 run 전체 직렬화 (한 번에 하나) | ❌ | Python | ✅ | 기각 |
|
|
36
|
-
| **C. 진입 가드 + 후발 no-team background 회피** (채택) | ✅ | Python 렌더(확정) | ✅ | 채택 |
|
|
37
|
-
|
|
38
|
-
- **A 기각**: flock 은 호출 프로세스 생존 중에만 유효한데 team 작업은 lead 행위라 PID-liveness 기반 advisory lock 으로만 구현된다. "선언만 하고 강제는 못 하는" 상태(CLAUDE.md "declaration vs enforcement" 위반 위험)이고, lead 가 acquire 를 빼먹으면 무효다.
|
|
39
|
-
- **B 기각**: stage 병렬성은 명시적 설계 기능이다([CLAUDE.md](../../../CLAUDE.md) "two concurrent implementation runs safely pick different ready stages — this is how stage parallelism works"). 전체 직렬화는 이 기능을 죽인다.
|
|
40
|
-
- **C 채택 근거 (핵심)**: 후발 run 이 team 을 **아예 만들지 않고** `run_in_background: true` + no `team_name` 으로 worker 를 dispatch 하면 `~/.claude/teams/` 를 건드리지 않아 race 가 원천 차단된다. 이 no-team 경로는 **okstra 에 이미 존재하는 Phase 5 fallback**([agents/SKILL.md:219](../../../agents/SKILL.md), [agents/SKILL.md:225](../../../agents/SKILL.md))이고, worker 완료는 team 유무와 무관하게 **result 파일 출현 polling** 으로 감지되므로([agents/SKILL.md:233](../../../agents/SKILL.md)) 분석 산출물은 동등하다. 잃는 것은 Teams split-pane 관찰성(FleetView)뿐이다. enforce 지점이 Python 렌더라 lead 협조에 기대지 않는다.
|
|
41
|
-
|
|
42
|
-
## 4. 설계
|
|
43
|
-
|
|
44
|
-
### 4.1 감지 (새 추적 없음)
|
|
45
|
-
|
|
46
|
-
`prepare_task_bundle` 은 implementation stage 선택 시 이미 `list_active_stage_numbers` 를 호출한다([scripts/okstra_ctl/run.py:1342](../../../scripts/okstra_ctl/run.py)). 이 호출은 task-key mutex 안에서 일어나고([scripts/okstra_ctl/run.py:1798](../../../scripts/okstra_ctl/run.py) `worktree_provision_mutex` → [run.py:1825](../../../scripts/okstra_ctl/run.py) stage 선택), 예약이 직렬화되므로 **후발 run 은 반드시 선발의 active stage 를 본다**(둘 다 못 보는 경우 없음).
|
|
47
|
-
|
|
48
|
-
판정: 선택된 stage 를 제외한 `reserved_stages` 가 비어있지 않으면 **동시-run**.
|
|
49
|
-
|
|
50
|
-
> 비대칭(한계): 선발 run 은 예약 시점에 후발이 아직 없어 후발을 영영 못 본다. 그러나 채택안에서는 **후발이 양보(no-team)** 하므로 선발의 team 이 보호된다 — 비대칭이 안전성을 깨지 않는다.
|
|
51
|
-
|
|
52
|
-
### 4.2 안전 기본값 — 동시-run 이면 no-team background 로 렌더
|
|
53
|
-
|
|
54
|
-
동시-run 감지 시 `prepare_task_bundle` 은 기본적으로 no-team 경로로 bundle 을 렌더한다:
|
|
55
|
-
|
|
56
|
-
- `render._build_team_creation_gate`(현 [render.py:1640](../../../scripts/okstra_ctl/render.py) 분기)에 동시-run 분기를 추가한다. "Team Creation Gate (BLOCKING)" 대신 **"Concurrent-run: no-team background" 블록**을 emit 한다. 이 블록은:
|
|
57
|
-
- lead 에게 `TeamCreate` 를 **시도하지 말고** 곧장 Phase 5(no-`team_name`, `run_in_background: true`)로 가라고 지시한다.
|
|
58
|
-
- team-state 에 `teamCreate: { attempted: false, status: "skipped", reason: "concurrent-run", concurrentStages: [<stages>] }` 를 **사전 기록**하라고 지시한다.
|
|
59
|
-
- `PrepareOutputs.extras`([scripts/okstra_ctl/run.py:309](../../../scripts/okstra_ctl/run.py))에 `concurrent_run: { detected: true, active_stages: [...] }` 를 담는다 — 대화형 분기(§4.3)와 비대화형 로그(§4.4)가 읽는다.
|
|
60
|
-
|
|
61
|
-
### 4.3 대화형 분기 (okstra-run 스킬)
|
|
62
|
-
|
|
63
|
-
스킬은 `extras.concurrent_run.detected` 가 참이면, conformance-waiver picker 와 **동형의 자체 3-옵션 recommendation picker**([skills/okstra-run/SKILL.md:192](../../../skills/okstra-run/SKILL.md))를 띄운다:
|
|
64
|
-
|
|
65
|
-
1. **(추천) 이대로 no-team background 로 진행** — 이미 §4.2 로 렌더된 bundle 을 그대로 사용. race 회피, Teams 관찰성만 포기.
|
|
66
|
-
2. **대기** — 지금 dispatch 를 보류한다. stage worktree·run-context 는 보존되고(registry release 는 worktree dir 을 보존한다 — [worktree_registry.py:283](../../../scripts/okstra_ctl/worktree_registry.py)) stage-key 예약은 이 stage 를 "내가 나중에 한다"로 유지한다. 다른 run 종료 후 **resume 명령으로 재개**(그때는 동시-run 이 아니므로 정상 team 경로). 스킬은 resume 명령을 산출물에 명시한다(메모리: next-step 내장).
|
|
67
|
-
3. **직접 입력** (마지막 옵션 고정 — 메모리: okstra run 입력은 항상 "직접 입력"으로 끝난다).
|
|
68
|
-
|
|
69
|
-
> 이 picker 는 스킬이 author 하는 것이라 run-prompt recommendation 규칙(1–2 추천 + 직접 입력)을 따른다 — wizard 가 emit 하는 `options[]` 가 아니므로 [skills/okstra-run/SKILL.md:52](../../../skills/okstra-run/SKILL.md) 의 "wizard option 을 줄이지 말라" 제약과 충돌하지 않는다.
|
|
70
|
-
|
|
71
|
-
### 4.4 비대화형 분기 (okstra.sh / node CLI)
|
|
72
|
-
|
|
73
|
-
사용자 상호작용이 불가하므로 **§4.2 안전 기본값(no-team background)으로 자동 진행** 하고, `extras.concurrent_run` 을 경고로 로그한다(`PROGRESS:` 또는 stderr). race 를 회피하면서 run 을 멈추지 않는다.
|
|
74
|
-
|
|
75
|
-
### 4.5 단일 수렴점
|
|
76
|
-
|
|
77
|
-
감지·렌더 결정은 `prepare_task_bundle` 안에서 한 번만 한다. 세 진입점(okstra-run 스킬 / okstra.sh / node CLI)은 모두 이 함수로 수렴하므로([CLAUDE.md](../../../CLAUDE.md) "All three must converge on `prepare_task_bundle()`"), 분기 UI(대화형 picker vs 비대화형 로그)만 진입점별로 다르고 결정 로직은 공유된다.
|
|
78
|
-
|
|
79
|
-
### 4.6 no-team fallback 의 새 legal 사유
|
|
80
|
-
|
|
81
|
-
현재 no-`team_name` fallback 은 "team-state `teamCreate.status == "error"` 일 때만 legal" 이다([agents/SKILL.md:231](../../../agents/SKILL.md)). 본 설계는 **`status == "skipped"` + `reason == "concurrent-run"`** 을 새 legal 사유로 추가한다. 수정 대상:
|
|
82
|
-
|
|
83
|
-
- [agents/SKILL.md:231](../../../agents/SKILL.md) — legal 사유에 concurrent-run skipped 추가.
|
|
84
|
-
- [prompts/profiles/_common-contract.md:53](../../../prompts/profiles/_common-contract.md) — Phase 7 teardown 의 "no-`team_name` fallback 에는 삭제할 team 이 없다" 조건이 skipped 에도 적용됨을 명시(이미 team 이 없으므로 silent-skip).
|
|
85
|
-
|
|
86
|
-
## 5. 구현 단위
|
|
87
|
-
|
|
88
|
-
| 단위 | 파일 | 변경 |
|
|
89
|
-
|---|---|---|
|
|
90
|
-
| 감지 플래그 | [scripts/okstra_ctl/run.py](../../../scripts/okstra_ctl/run.py) | stage 선택 결과에서 `reserved_stages\{선택}` 를 `extras.concurrent_run` 으로 전파 |
|
|
91
|
-
| 렌더 분기 | [scripts/okstra_ctl/render.py:1640](../../../scripts/okstra_ctl/render.py) | 동시-run 시 no-team 블록 + 사전 team-state 지시 emit |
|
|
92
|
-
| 대화형 picker | [skills/okstra-run/SKILL.md](../../../skills/okstra-run/SKILL.md) | `extras.concurrent_run` 분기 → 3-옵션 picker |
|
|
93
|
-
| no-team legal | [agents/SKILL.md:231](../../../agents/SKILL.md), [prompts/profiles/_common-contract.md:53](../../../prompts/profiles/_common-contract.md) | concurrent-run skipped 를 legal 사유로 |
|
|
94
|
-
| 빌드 | `npm run build` | 위 소스를 `runtime/` 로 동기화(직접 편집 금지) |
|
|
95
|
-
|
|
96
|
-
## 6. 한계 (명시)
|
|
97
|
-
|
|
98
|
-
- **후발이 대기·background 를 거부하고 team 생성을 강행** 하면 race 창은 다시 열린다. 진입 가드는 advisory 경계까지가 한계다(임계구역 lock 을 안 쓰기로 한 §3-A 의 귀결).
|
|
99
|
-
- **이미 발생한 race 의 복구**(config.json 소실 상태)는 본 설계 범위 밖이다. team_reconcile / 재생성은 별도 작업으로 다룬다.
|
|
100
|
-
- **미검증**: no-team background 가 dispatch→완료→convergence→리포트 전체 사이클을 끝까지 도는 것은 실제 run 으로만 최종 확인된다. 현재는 정적 설계 수준이다(화면에서 해당 dispatch 경로 자체는 관찰됨).
|
|
101
|
-
|
|
102
|
-
## 7. 테스트 계획
|
|
103
|
-
|
|
104
|
-
- **단위**: `list_active_stage_numbers` 가 비어있지 않을 때 `prepare_task_bundle` 결과의 `extras.concurrent_run.detected` 가 참이 되는지([tests/](../../../tests/)).
|
|
105
|
-
- **단위**: 동시-run 일 때 render 가 Team Creation Gate 대신 no-team 블록을 emit 하고 사전 team-state 지시(`status: "skipped", reason: "concurrent-run"`)를 포함하는지.
|
|
106
|
-
- **단위**: 동시-run 이 아닐 때(=`reserved_stages` 가 선택 stage 뿐) 기존 Team Creation Gate 가 그대로 나오는지(회귀 방지).
|
|
107
|
-
- **e2e**: 같은 task-key 두 implementation run 을 직렬로 시뮬레이션(첫 run 의 stage-key 예약을 active 로 둔 채 둘째 run prepare)해 둘째가 no-team 경로로 가는지([tests-e2e/](../../../tests-e2e/) scenario 추가).
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# git-reconcile — stale SHA 회복 3단 방어 — 설계
|
|
2
|
-
|
|
3
|
-
- 작성일: 2026-06-10
|
|
4
|
-
- 범위: okstra 밖에서 일어난 git 히스토리 변경(PR 리뷰 amend, rebase, squash merge, 외부 커밋, branch 삭제) 이후 `implementation` run 을 손편집 없이 이어가게 한다. 저장된 commit SHA 가 stale 해졌을 때의 **감지 → 내용 기반 자동 화해 → 확인 보정** 경로를 추가한다.
|
|
5
|
-
- 비범위
|
|
6
|
-
- **SHA → branch 명 저장 전환 없음** — SHA 는 계속 기록의 정본(불변 스냅샷)이다. branch 는 사람이 의도를 다시 알려줄 때의 *입력 수단*일 뿐이다. branch tip 은 가변이라 anchor 고정·done 스냅샷·ancestor 게이트의 불변식과 양립하지 않고, squash merge 후에는 branch tip 도 ancestor 가 아니므로 사용자 페인을 해결하지 못한다.
|
|
7
|
-
- okstra 가 머지를 대행하지 않는다 — 보정은 *기록*(`consumers.jsonl` / registry)의 화해이지 git 조작이 아니다.
|
|
8
|
-
- `implementation` 외 phase 의 task-key worktree 모델 불변.
|
|
9
|
-
- 관계: [`2026-06-06-stage-worktree-isolation-design.md`](2026-06-06-stage-worktree-isolation-design.md)의 base 결정 규칙(§2.2)·anchor 1회 고정(§3.2) 위에 선다. 그 설계가 도입한 ancestor 게이트의 known gap — 히스토리 재작성 시 회복 경로 부재 — 를 본 설계가 해소한다. [`scripts/okstra_ctl/reconcile.py`](../../../scripts/okstra_ctl/reconcile.py)는 run 상태(recent.jsonl) 보정 전용으로 별개 개념이며 건드리지 않는다.
|
|
10
|
-
|
|
11
|
-
## 1. 동기
|
|
12
|
-
|
|
13
|
-
stage 격리 설계는 base 를 SHA 로 고정한다: 독립 stage = anchor([`set_implementation_base`](../../../scripts/okstra_ctl/worktree_registry.py:199), 1회 고정 후 무시), 단일 의존 = 선행 done `head_commit`([run.py:526](../../../scripts/okstra_ctl/run.py:526)), 다중 의존 = `git merge-base --is-ancestor` 검증([run.py:454](../../../scripts/okstra_ctl/run.py:454), [run.py:506](../../../scripts/okstra_ctl/run.py:506)). 이 SHA 들이 okstra 밖의 작업으로 stale 해지면:
|
|
14
|
-
|
|
15
|
-
1. **rebase / squash merge** — 커밋 ID 가 바뀌어 저장된 SHA 가 candidate 의 ancestor 가 아니게 된다. 내용은 머지됐는데도 `PrepareError`("not merged")로 거부된다.
|
|
16
|
-
2. **PR 리뷰 반영 amend** — 내용까지 바뀐 새 커밋. 저장된 done SHA 는 검증 시점의 코드를 가리키지만 사용자 의도는 리뷰 반영본이다.
|
|
17
|
-
3. **SHA 소멸** — branch 삭제 + GC 후 [`_resolve_commit_sha`](../../../scripts/okstra_ctl/worktree.py:260) 실패 → [worktree.py:781](../../../scripts/okstra_ctl/worktree.py:781) 에서 거부.
|
|
18
|
-
|
|
19
|
-
현재 세 경우 모두 회복 경로가 없다. 사용자가 할 수 있는 것은 `~/.okstra/worktrees/registry.json` / `consumers.jsonl` 손편집뿐이다.
|
|
20
|
-
|
|
21
|
-
## 2. 핵심 원칙
|
|
22
|
-
|
|
23
|
-
1. **SHA = 정본, branch = 입력 수단.** 저장 포맷은 바꾸지 않는다. stale 시 branch(또는 사용자 지정 ref)에서 SHA 를 *다시 캡처*해 새 기록으로 append 한다.
|
|
24
|
-
2. **자동화 수위 (사용자 결정 2026-06-10): 내용 동일 = 자동, 내용 변경 = 확인.** patch-id 로 내용 동일성이 *증명*되는 경우(rebase / cherry-pick / 깨끗한 squash)만 확인 없이 자동 통과·자동 재기록하고 로그를 남긴다. 내용이 바뀐 경우(리뷰 반영 amend 등)는 반드시 사용자 확인(picker)을 거친다. 증명 없는 자동 진행은 금지 — 검증 안 된 커밋이 base 에 섞이는 것을 차단한다.
|
|
25
|
-
3. **append-only 유지.** 보정은 `consumers.jsonl` 에 새 done row 를 append 하는 방식이다(기존 row 수정/삭제 없음). 이를 위해 done-row 읽기를 last-wins 로 통일한다(§3.4 — 선행 조건).
|
|
26
|
-
4. **단일 reference point.** 감지·화해·보정 로직은 Python 모듈 1곳(`git_reconcile.py`)에 두고, prepare 경로·`git-reconcile` subcommand·skill picker 는 모두 그것을 소비한다.
|
|
27
|
-
|
|
28
|
-
## 3. 구성 요소
|
|
29
|
-
|
|
30
|
-
### 3.1 content-equivalence 검사기 — `scripts/okstra_ctl/git_reconcile.py` (신규)
|
|
31
|
-
|
|
32
|
-
`content_merged(project_root, commit, candidate, base) -> MatchResult`
|
|
33
|
-
|
|
34
|
-
1. `git merge-base --is-ancestor commit candidate` 성공 → 기존대로 통과(`ancestor`).
|
|
35
|
-
2. 실패 시 patch-id fallback, 두 granularity:
|
|
36
|
-
- **커밋 단위**: `git show <commit> | git patch-id --stable` 을 `merge-base(commit, candidate)..candidate` 구간 각 커밋의 patch-id 와 비교 — rebase / cherry-pick 커버.
|
|
37
|
-
- **범위 합산**: `git diff <base> <commit> | git patch-id --stable` (stage 작업 전체를 한 diff 로) 을 candidate 구간 커밋들의 patch-id 와 비교 — N 커밋이 1 커밋으로 합쳐진 squash merge 커버. `base` 는 해당 stage 의 base 기록(anchor 또는 선행 done SHA).
|
|
38
|
-
3. 일치 시 candidate 히스토리에서 매칭된 커밋 SHA 를 반환(`patch-equivalent`, 재기록에 사용). 불일치 → `not-merged`.
|
|
39
|
-
|
|
40
|
-
`commit` 자체가 unresolvable 이면 patch-id 를 계산할 수 없으므로 무조건 confirm 급이다(§3.2).
|
|
41
|
-
|
|
42
|
-
### 3.2 stale 분류기 — 같은 모듈
|
|
43
|
-
|
|
44
|
-
`classify(project_root, plan_run_root, task_key) -> StaleReport(JSON 직렬화 가능)`
|
|
45
|
-
|
|
46
|
-
task 단위로 다음을 점검하고 항목별 `auto` / `confirm` / `ok` 를 매긴다:
|
|
47
|
-
|
|
48
|
-
| 점검 | stale 조건 | 분류 |
|
|
49
|
-
|---|---|---|
|
|
50
|
-
| anchor (`implementation_base_commit`) | unresolvable | `confirm` (재고정 필요) |
|
|
51
|
-
| 각 stage 의 최신 done `head_commit` | unresolvable | `confirm` |
|
|
52
|
-
| 〃 | resolvable 이지만 해당 stage branch(`-s<N>`) tip 과 불일치 + patch-id 동등 | `auto` (tip 으로 재기록) |
|
|
53
|
-
| 〃 | 〃 + patch-id 불일치 (내용 변경) | `confirm` |
|
|
54
|
-
| 다중 의존 ancestor 게이트 | ancestor 아님 + `content_merged`=`patch-equivalent` | `auto` (매칭 SHA 로 재기록) |
|
|
55
|
-
| 〃 | ancestor 아님 + `not-merged` | `confirm` |
|
|
56
|
-
|
|
57
|
-
stage branch 가 이미 삭제된 경우 tip 비교는 건너뛰고 candidate(task-key worktree HEAD) 대상 `content_merged` 만 본다.
|
|
58
|
-
|
|
59
|
-
### 3.3 보정 명령 — `git-reconcile` subcommand
|
|
60
|
-
|
|
61
|
-
- Python 구현 1곳: `git_reconcile.py` 의 `check()` / `apply()`. Node CLI 는 `bin/okstra` 서브커맨드 표에 `git-reconcile` 추가 + `src/git-reconcile.mjs` 가 [`_python-helper.mjs`](../../../src/_python-helper.mjs)로 패스스루(기존 `migrate` 패턴). `okstra.sh` 는 run 런처(flag 기반)라 ops subcommand dispatch 가 없으므로 배선하지 않는다 — ops 명령은 node CLI 전용(기존 `migrate` 와 동일).
|
|
62
|
-
- `--check`: §3.2 리포트를 JSON 으로 출력 (기본 indent — 사람 가독, `--json` 시 compact 단일 라인 — 기계 파싱). confirm 항목 잔존 시 exit 2.
|
|
63
|
-
- `--apply`: `auto` 항목 일괄 보정. `confirm` 항목은 `--stage <N> --use-ref <branch|sha>` 로 개별 보정(ref 는 즉시 SHA 로 resolve 해 기록), `--reset-anchor <ref>` 로 anchor 재고정.
|
|
64
|
-
- 보정 = `consumers.jsonl` 에 `status="done"` row 재-append. 추가 필드: `reconciled: true`, `reconcile_reason: "<auto-patch-id|user-ref>"`, `replaced_commit: "<old sha>"`. [`append_consumer`](../../../scripts/okstra_ctl/consumers.py:36)에 optional 필드 전달을 허용한다.
|
|
65
|
-
|
|
66
|
-
### 3.4 done-row 읽기 last-wins 통일 (선행 조건)
|
|
67
|
-
|
|
68
|
-
현재 읽기 의미가 갈라져 있다: [run.py:489](../../../scripts/okstra_ctl/run.py:489)·[run.py:527](../../../scripts/okstra_ctl/run.py:527)은 첫 행 우선, [run.py:556](../../../scripts/okstra_ctl/run.py:556)(`done_by_stage` dict)은 마지막 행 우선. 보정 row 가 효력을 가지려면 last-wins 가 유일한 의미여야 한다.
|
|
69
|
-
|
|
70
|
-
- [`consumers.py`](../../../scripts/okstra_ctl/consumers.py)에 `latest_done_by_stage(rows) -> Dict[int, row]` helper 신설.
|
|
71
|
-
- 소비 지점 전부를 helper 로 수렴: [run.py:489](../../../scripts/okstra_ctl/run.py:489), [run.py:527](../../../scripts/okstra_ctl/run.py:527), [run.py:556](../../../scripts/okstra_ctl/run.py:556), [run.py:1467](../../../scripts/okstra_ctl/run.py:1467) 경로. ([`backfill_done_from_carry`](../../../scripts/okstra_ctl/consumers.py:115)의 `done_stages` set 은 존재 여부만 보므로 영향 없음.)
|
|
72
|
-
|
|
73
|
-
### 3.5 anchor 재고정
|
|
74
|
-
|
|
75
|
-
[`set_implementation_base`](../../../scripts/okstra_ctl/worktree_registry.py:199)의 "이미 있으면 무시" 규칙(동시 first-stage 수렴용)은 유지. `worktree_registry.py` 에 `reset_implementation_base(..., commit)` 를 추가하되 **호출자는 §3.3 `--reset-anchor` 하나뿐**이다 — prepare 경로가 자동으로 anchor 를 움직이는 일은 없다.
|
|
76
|
-
|
|
77
|
-
### 3.6 prepare / skill 배선
|
|
78
|
-
|
|
79
|
-
- **prepare 경로 자동 화해**: [`_resolve_stage_base_commit`](../../../scripts/okstra_ctl/run.py:465)과 final-verification 의 merged 검사([`_is_ancestor`](../../../scripts/okstra_ctl/run.py:1439), [run.py:1488](../../../scripts/okstra_ctl/run.py:1488))가 ancestor 실패 시 `content_merged` 를 호출한다. `patch-equivalent` 면 통과 + 보정 row 자동 append(다음 run 부터는 ancestor 검사로 바로 통과). `not-merged`/unresolvable 이면 `PrepareError` 메시지에 stale 리포트 요약과 `okstra git-reconcile --check` / `--apply` 명령 예시를 포함한다.
|
|
80
|
-
- **skill picker**: `skills/okstra-run/SKILL.md` 에 단계 추가 — prepare 가 위 안내를 담은 `PrepareError` 로 실패하면 `git-reconcile --check --json` 을 실행해 confirm 항목별 picker 를 제시한다. 옵션 구성은 추천 + 직접 입력 규칙을 따른다: ① "stage branch 현재 tip 으로 재기록 (추천)" ② "다른 ref 직접 입력" ③ "중단". 선택 시 `--apply --stage N --use-ref <ref>` 실행 후 prepare 재시도.
|
|
81
|
-
|
|
82
|
-
> declaration ↔ enforcement: "내용 변경은 확인 필수" 의 강제 지점은 **`apply()` 런타임 가드**다 — `confirm` 분류 항목은 `--use-ref` 없이 절대 보정되지 않으며, 이를 검증하는 pytest 가 박제한다. 프로파일/스킬 문구만으로 끝내지 않는다.
|
|
83
|
-
|
|
84
|
-
## 4. 시나리오별 동작
|
|
85
|
-
|
|
86
|
-
| okstra 밖에서 일어난 일 | 동작 |
|
|
87
|
-
|---|---|
|
|
88
|
-
| stage branch rebase (내용 동일) | patch-id 증명 → 자동 통과 + tip SHA 재기록 |
|
|
89
|
-
| squash merge 후 branch 삭제 | 범위 patch-id 일치 → 자동 통과 + squash SHA 재기록 |
|
|
90
|
-
| PR 리뷰 반영 amend (내용 변경) | `confirm` → picker → 사용자 ref 로 재기록 후 진행 |
|
|
91
|
-
| done SHA GC 로 소멸 | `confirm` → picker (ref 직접 입력) |
|
|
92
|
-
| anchor unresolvable | `confirm` → `--reset-anchor` 안내 |
|
|
93
|
-
| 외부 커밋이 단순히 쌓임 (재작성 없음) | stale 아님 — 기존 동작 그대로 |
|
|
94
|
-
|
|
95
|
-
## 5. 테스트
|
|
96
|
-
|
|
97
|
-
- **pytest (실 git)**: `mktemp` git repo 에서 rebase / cherry-pick / squash merge / amend / branch 삭제를 실제로 만들어 `content_merged` 와 `classify` 의 분류를 검증. mock 금지 — patch-id·merge-base 는 실제 git 실행으로만 검증된다.
|
|
98
|
-
- **last-wins 회귀**: 같은 stage 에 done row 2개(원본 + reconciled)를 append 한 뒤 base 해석·whole-task 게이트가 마지막 row 를 쓰는지 검증.
|
|
99
|
-
- **enforcement 테스트**: `confirm` 항목이 `--use-ref` 없이 `apply()` 로 보정되지 않음을 박제.
|
|
100
|
-
- **e2e**: `tests-e2e/scenario-<id>-git-reconcile-squash.sh` — stage1 done → squash merge → branch 삭제 → stage2 prepare 가 자동 통과하는 흐름.
|
|
101
|
-
|
|
102
|
-
## 6. 미해결 / 후속
|
|
103
|
-
|
|
104
|
-
- 다중 stage 가 섞인 octopus 머지·부분 머지(stage diff 의 일부만 반영)는 patch-id 로 증명 불가 → `confirm` 으로 떨어진다. 의도된 보수성.
|
|
105
|
-
- `--apply` 의 wizard(okstra.sh 대화형) 통합은 skill picker 가 안정된 뒤 별도 plan.
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# Lead-Worker 완료 인지: self-scheduled 폴링 설계
|
|
2
|
-
|
|
3
|
-
- 작성일: 2026-06-10
|
|
4
|
-
- 상태: 설계 승인됨 (구현 전, 실측 게이트 미통과)
|
|
5
|
-
- 관련 미구현 항목: [agents/TODO.md](../../../agents/TODO.md) "수정 B — Leader-side 워커 soft timeout", "수정 C 후보 — worker-results 파일 폴링 루프"
|
|
6
|
-
|
|
7
|
-
## 1. 문제 (런타임으로 확정)
|
|
8
|
-
|
|
9
|
-
okstra lead 가 워커를 dispatch 한 뒤, **워커가 작업을 끝냈는데도 lead 가 완료를 인지하지 못하고 정지**한다. lead 는 "완료 통지를 기다립니다" 같은 자연어 상태로 턴을 종료하고, 사용자가 "아직도?" 같은 입력을 **수동으로** 넣어야만 재개된다.
|
|
10
|
-
|
|
11
|
-
런타임 증거 — `fontradar-v2-api:calcule-des-prix-1-1:dev-9185` stage 2 implementation 런 (lead 세션 jsonl `82424fb3-…`):
|
|
12
|
-
|
|
13
|
-
| dispatch | Agent tool_result | 사용자가 수동으로 깨운 입력 |
|
|
14
|
-
|---|---|---|
|
|
15
|
-
| claude executor (10:15) | `Spawned successfully. agent_id: claude-worker-2@…` | "아직도?" (15:37) |
|
|
16
|
-
| codex verifier (15:55) | `Spawned successfully. agent_id: codex-worker@…` | "아직도?" (16:21) |
|
|
17
|
-
| report-writer (16:26) | `Spawned successfully. agent_id: report-writer@…` | "아지도?" (16:37) |
|
|
18
|
-
|
|
19
|
-
**한 런에서 3번**, 모든 워커 dispatch 마다 발생했다. report-writer 는 dispatch 직후(16:26) 이미 16:33 에 보고서 파일을 다 썼지만, lead 는 사용자의 16:37 입력 전까지 11분간 정지했다. 워커가 *안 끝난 것*이 아니라 워커는 *끝났는데 lead 가 모른 것*이다.
|
|
20
|
-
|
|
21
|
-
## 2. 근본 원인
|
|
22
|
-
|
|
23
|
-
- Claude Code Agent Teams 에서 `Agent(team_name: …)` dispatch 는 **비동기 spawn-and-return** 이다 (teammate = 별도 Claude 인스턴스). 호출은 `Spawned successfully` 만 즉시 반환하고, 완료는 mailbox 의 idle 알림으로 push 되어야 한다.
|
|
24
|
-
- 그러나 okstra 의 dispatch 명세는 **동기 blocking 을 전제**로 작성돼 있다 — [agents/workers/claude-worker.md:89](../../../agents/workers/claude-worker.md) "Agent() call blocks until you return", [agents/SKILL.md:381](../../../agents/SKILL.md) "when the Agent call finally returns".
|
|
25
|
-
- 실험적 기능인 Agent Teams 의 idle 자동 알림이 lead 를 깨우지 못하는 경우가 있다(공식 문서 Limitations "Task status can lag"). 그 누락이 발생하면 lead 를 깨울 이벤트가 사라지고 무한 정지한다.
|
|
26
|
-
- 영향 범위는 **lead 가 띄우는 모든 teammate** 다 (claude / codex / gemini / report-writer). codex/gemini wrapper 의 `BashOutput` 폴링은 *wrapper subagent ↔ CLI* 레이어일 뿐, *lead ↔ wrapper subagent* dispatch 레이어는 별개로 비동기다 (위 표의 codex 행이 증거).
|
|
27
|
-
|
|
28
|
-
## 3. 검토한 대안과 채택 근거
|
|
29
|
-
|
|
30
|
-
공식 문서(`sub-agents.md`, `agent-teams.md`, `headless.md`, `scheduled-tasks.md`) 근거로 세 방향을 비교했다.
|
|
31
|
-
|
|
32
|
-
| 방향 | 병렬 유지 | mailbox 의존 | 양쪽 경로 작동 | 자동(수동 nudge 제거) | 런타임 보장 |
|
|
33
|
-
|---|---|---|---|---|---|
|
|
34
|
-
| A. foreground 동기 dispatch (`team_name` 제거) | ❌ 순차화 | — | ✅ | ✅ | 문서 명시 보장 |
|
|
35
|
-
| B. 워커가 `SendMessage` 로 완료 통지 | ✅ | ❌ idle 알림과 같은 mailbox → 동일 누락 위험 | ❌ | △ | 미보장 (stopped lead auto-resume 문장 부재) |
|
|
36
|
-
| **C. self-scheduled 폴링** (채택) | ✅ | ✅ 무관 (파일 출현으로 판정) | ✅ | ✅ | session-scoped, interactive 전제 |
|
|
37
|
-
|
|
38
|
-
- **방향 B 기각**: "정지한 lead 가 inbound SendMessage 로 auto-resume 한다"는 보장이 문서에 없다. auto-resume 보장은 *부모→자식(stopped subagent)* 방향뿐이다. 게다가 SendMessage 와 idle 알림은 동일한 "Mailbox" 채널이라 같은 누락 위험을 공유한다.
|
|
39
|
-
- **방향 C 채택 가능 근거 (핵심)**: self-scheduled 폴링은 "idle 상태로 살아있는 interactive 세션"에서만 동작하고 headless `claude -p` 에서는 불가하다. 그런데 okstra lead 는 **양쪽 경로 모두 interactive** 다:
|
|
40
|
-
- okstra.sh spawn 경로: [scripts/okstra.sh:173-175](../../../scripts/okstra.sh) 가 `claude --model … --session-id … "$PROMPT"` 를 `exec` 한다. `-p`/`--print` 가 **없다** → 대화형 세션, 턴 종료 후 idle 로 생존.
|
|
41
|
-
- in-session(`okstra-run`) 경로: 사용자의 대화형 세션 안에서 동작.
|
|
42
|
-
- 방향 C 는 병렬 유지 + mailbox 무관 + 양쪽 경로 + 수동 nudge 제거를 모두 만족하는 유일한 후보다.
|
|
43
|
-
|
|
44
|
-
## 4. 설계
|
|
45
|
-
|
|
46
|
-
### 4.1 메커니즘
|
|
47
|
-
|
|
48
|
-
lead 가 워커를 (기존대로) 비동기 dispatch 한 뒤:
|
|
49
|
-
|
|
50
|
-
1. 자기 자신을 주기적으로 깨우는 **단일 스케줄**을 건다 (Monitor 툴 우선, cron 폴백 — 4.4 참조).
|
|
51
|
-
2. 턴을 종료한다.
|
|
52
|
-
3. 스케줄이 lead 를 깨울 때마다, dispatch 한 워커들의 `worker-results` 파일 출현 + 헤더 검증으로 **완료를 직접 확인**한다 (mailbox/idle 알림에 의존하지 않는다).
|
|
53
|
-
4. 미완이면 다시 깨우도록 두고, 종료 조건 충족 시 스케줄을 해제하고 다음 phase 로 진행한다.
|
|
54
|
-
|
|
55
|
-
### 4.2 복수 워커 처리
|
|
56
|
-
|
|
57
|
-
Phase 4/5 는 보통 claude·codex·gemini 를 **병렬 N개** 띄운다. 워커가 몇 개든:
|
|
58
|
-
|
|
59
|
-
- **스케줄은 하나만** 건다. 워커마다 스케줄을 거는 것이 아니라, **하나의 wakeup 이 매번 N개 결과 파일 전체를 확인**한다.
|
|
60
|
-
- 추적 대상 = lead 가 dispatch 한 워커 전원의 `**Result Path:**` 집합 (이미 run-manifest / launch 프롬프트에 존재).
|
|
61
|
-
- 매 wakeup: 미완 집합의 각 파일을 확인 → 출현 + 헤더 검증 통과한 워커는 완료 집합으로 이동.
|
|
62
|
-
- in-process claude · CLI wrapper codex/gemini 혼합이든, 전부 "결과 파일 출현"이라는 단일 신호로 통일 감지한다. 파일 경로만 다를 뿐 폴링 로직은 N 에 무관하게 동일하다.
|
|
63
|
-
- 동일 패턴이 Phase 5.5 convergence 라운드(라운드마다 가변 워커 집합)에도 그대로 적용된다.
|
|
64
|
-
|
|
65
|
-
### 4.3 종료 lifecycle (BLOCKING)
|
|
66
|
-
|
|
67
|
-
스케줄을 반드시 해제해야 한다 — 해제하지 않으면 cron 이 7일 만료까지 좀비로 발화하며 토큰을 태우고 다음 런까지 방해한다.
|
|
68
|
-
|
|
69
|
-
| 상황 | 동작 |
|
|
70
|
-
|---|---|
|
|
71
|
-
| 전원 완료 (미완 집합이 빔) | **스케줄 즉시 해제** → 다음 phase 진행 |
|
|
72
|
-
| 부분 완료 | 미완 워커만 계속 대기 (재예약) |
|
|
73
|
-
| 워커별 deadline 초과 | 해당 워커를 `timeout` 으로 기록 → 미완 집합에서 제거 → redispatch 또는 진행 |
|
|
74
|
-
| 에러 / 중단 | 어떤 경로로 끝나든 **스케줄 반드시 해제** (좀비 방지) |
|
|
75
|
-
|
|
76
|
-
- **워커별 soft timeout**: codex/gemini 는 claude 보다 느릴 수 있으므로 deadline 은 워커별이다. [agents/TODO.md:121-153](../../../agents/TODO.md) 의 task-type 별 예상 duration 표(요구사항-탐색 10분, 오류-분석 15분, 구현-계획 20분 등 × 2배)를 그대로 deadline 산정에 사용한다. 즉 미구현 "수정 B" 가 방향 C 의 deadline 조건으로 통합된다 — 별도 메커니즘이 아니다.
|
|
77
|
-
- cron 7일 만료는 최후 백스톱일 뿐 정상 해제 경로가 아니다.
|
|
78
|
-
|
|
79
|
-
### 4.4 메커니즘 선택: Monitor vs cron
|
|
80
|
-
|
|
81
|
-
- **Monitor 툴(우선 후보)**: 완료를 stdout 으로 내보내는 background 스크립트를 스트리밍 감시 → 폴링 자체를 없앰. 매 iteration 마다 전체 프롬프트를 재실행하는 cron 보다 토큰·캐시 효율이 높고 반응이 빠르다.
|
|
82
|
-
- **cron / `/loop`(폴백)**: 최소 granularity 1분, jitter 는 인터벌의 최대 절반. 고정 인터벌이면 2~5분, 워커가 10~20분 걸리므로 self-paced(`/loop` bare) 가 토큰 효율적.
|
|
83
|
-
- 최종 선택은 §5 실측 게이트 결과로 확정한다.
|
|
84
|
-
|
|
85
|
-
## 5. 검증 게이트 (Rule 6 — 구현 1단계, BLOCKING)
|
|
86
|
-
|
|
87
|
-
지금까지의 결론은 전부 **공식 문서 분석 + 코드 확인 기반 정적 결론(미실행)** 이다. 구현의 첫 단계는 다음을 interactive 세션에서 **실측**하는 것이다:
|
|
88
|
-
|
|
89
|
-
1. lead 가 dispatch 한 비동기 워커(teammate/background subagent)가 별도 세션에서 도는 동안, Monitor/cron self-wakeup 이 lead 를 자동 재개시키는가.
|
|
90
|
-
2. 깨어난 lead 가 그 워커의 결과 파일 출현을 Bash 로 감지할 수 있는가.
|
|
91
|
-
3. Monitor 와 cron 중 어느 쪽이 실제로 동작하며 토큰 효율이 나은가.
|
|
92
|
-
|
|
93
|
-
이 실측이 통과하기 전에는 SKILL 명세 변경을 확정하지 않는다. 실패 시 폴백은 방향 A(report-writer 등 단일 워커는 `team_name` 없는 foreground 동기 dispatch)이며, 이는 문서가 명시적으로 보장하는 유일한 경로다.
|
|
94
|
-
|
|
95
|
-
> **갱신 (2026-06-10): 게이트 통과.** 실측 결과 채택 메커니즘은 cron/Monitor 가 아니라 **`Bash run_in_background` + `until` 루프**로 확정됐다. harness-tracked background 완료가 정지 lead 를 자동 재개시키며(실험 1), 단일 background 폴링이 복수 워커 결과 파일을 추적한다(실험 2). background task 는 완료 시 자가 소멸하므로 §4.3 의 "스케줄 해제 / 좀비 cron 방지" 부담이 소멸하고, §4.4 의 Monitor/cron 은 대안으로 격하된다. 상세: [실측 기록](../plans/2026-06-10-lead-worker-completion-polling-PROBE.md).
|
|
96
|
-
|
|
97
|
-
## 6. 변경 범위 (영향 파일)
|
|
98
|
-
|
|
99
|
-
- [agents/SKILL.md](../../../agents/SKILL.md) — Phase 4/5/6 dispatch 후 "self-scheduled 폴링으로 완료 인지" 루프 명세 추가. 동기 전제 문구(:381 "when the Agent call finally returns") 정정.
|
|
100
|
-
- [agents/workers/claude-worker.md:89](../../../agents/workers/claude-worker.md) — "Agent() call blocks until you return" 동기 전제 정정.
|
|
101
|
-
- [skills/okstra-report-writer/SKILL.md](../../../skills/okstra-report-writer/SKILL.md) — Phase 6 dispatch 후 완료 폴링 명세.
|
|
102
|
-
- [skills/okstra-team-contract/SKILL.md](../../../skills/okstra-team-contract/SKILL.md) — terminal status / soft timeout 규칙 보강.
|
|
103
|
-
- [skills/okstra-convergence/SKILL.md](../../../skills/okstra-convergence/SKILL.md) — 라운드별 reverify 집합 폴링 적용.
|
|
104
|
-
- [templates/reports/settings.template.json](../../../templates/reports/settings.template.json) — cron/Monitor 관련 도구 권한 seed (필요 시).
|
|
105
|
-
- [agents/TODO.md](../../../agents/TODO.md) — "수정 B" / "수정 C 후보" 를 본 설계로 해소·통합.
|
|
106
|
-
|
|
107
|
-
변경은 모두 **명세(프롬프트/SKILL) 레이어** 중심이며, 동기 전제를 비동기-폴링 모델로 정정하는 것이 핵심이다. 사용자 머신 전파를 위해 seed/template 파일에 반영한다 (personal `.claude/` 아님).
|
|
108
|
-
|
|
109
|
-
## 7. 미해결 / 리스크
|
|
110
|
-
|
|
111
|
-
- 프롬프트 캐시 TTL 의 구체 수치는 공식 문서에 없어 미확인 — 폴링 간격 최적값은 실측으로 조정.
|
|
112
|
-
- Monitor 툴이 별도-세션 워커의 완료를 감지하는 정확한 방식(파일 watch 스크립트 형태)은 실측 단계에서 확정.
|
|
113
|
-
- in-session 경로에서 lead 가 okstra 스킬 흐름 한가운데 cron 을 걸 때, wakeup 프롬프트가 okstra phase 흐름을 자연스럽게 이어가도록 self-describing 해야 한다 (구현 plan 에서 설계).
|