Skip to content

feat(orchestrate): replace the Wave barrier with a ready-set scheduler (PR 1/2) - #59

Merged
choiyounggi merged 8 commits into
mainfrom
feat/orchestrate-ready-set-scheduler
Aug 7, 2026
Merged

feat(orchestrate): replace the Wave barrier with a ready-set scheduler (PR 1/2)#59
choiyounggi merged 8 commits into
mainfrom
feat/orchestrate-ready-set-scheduler

Conversation

@choiyounggi

Copy link
Copy Markdown
Owner

Wave execution made every task in a batch wait for its slowest member, and the concurrency cap was a hardcoded 4 with no configuration path. This replaces both with a dependency graph plus slot accounting: a task runs the moment its own dependencies are approved and a slot is free.

Spec: docs/superpowers/specs/2026-08-07-orchestrate-ready-set-scheduler-design.md · Plan: docs/superpowers/plans/2026-08-07-orchestrate-ready-set-scheduler.md

What changed

ready-set.sh (new) — reads .orchestration/graph.json plus .orchestration/status/*.json and answers one question: which tasks may be dispatched right now. It launches nothing; the coordinator keeps every judgement call.

exit meaning coordinator does
0 dispatch these (stdout: one id per line) dispatch
2 nothing dispatchable, work in flight wait for an event
3 nothing dispatchable, nothing running, unfinished tasks remain DEADLOCK — do not wait, report
4 graph or status unreadable refuse, do not guess
5 every task terminal go to Phase 5

Exit 3 is the safety property this design turns on. Under Waves, a failed task visibly stalled its batch; under a ready-set its dependents simply never become ready, and a naive loop reads that as "quiet". That is the same failure shape as the orca-wait.sh bug fixed in v1.4.1 — an outage that looks exactly like nothing happening.

Dependencies count as satisfied only at approved or higher, never at impl_done. A task built on an unreviewed interface has to be redone when rework changes that signature. This narrows the Wave model's "previous Wave fully approved" guarantee from a global barrier to a per-task wait rather than dropping it.

Slots are held from dispatch until a terminal stateplan_ready and impl_done (review pending) count as held. Otherwise unreviewed tasks pile up beside a stream of new ones and the cap stops meaning anything.

watch-status.sh --tasks <csv> (new option) — scopes the scan to the ids currently being tracked. Without it, tasks approved in earlier rounds satisfy expected=1 immediately and the wait spins. argc is now captured after option parsing; leaving it in place would have silently inverted the LO_PHASE_TIMEOUTS precedence rule, and a test pins that.

The cap is no longer hardcoded. The coordinator proposes a slot count at Gate 1 and must state what the number protects — coordinator attention and API usage/budget, neither of which is queryable, which is why it is a judgement rather than a computation. LO_MAX_SESSIONS is an upper bound that overrides the proposal, in the same family as LO_PHASE_TIMEOUTS.

SKILL.md Phases 2, 3, 4 and Re-entry are rewritten into one dispatch loop. Re-entry got simpler: there is no Wave index to restore, so reading the graph plus the status files and running ready-set.sh is the restored state.

Tests

407 passing, 0 failing (387 before). 20 new: 13 pinning ready-set.sh (normal, error, boundary — including a failed dependency and a cycle both landing on exit 3), 5 on --tasks scoping with an all-N regression guard, 2 doc-contract tests pinning the loop's structure rather than its keywords. No existing assertion was weakened or removed.

Review

Each task got an independent review; the branch then got a whole-branch review. One Critical finding was caught and fixed: folding Phase 3 and Phase 4 into a single loop dropped the implement-prompt delivery step, so a coordinator would have watched tasks reach plan_ready and then waited forever for an impl_done nobody requested. Every test passed while that hole was open — it was only visible by reading the skill as something to execute.

Mid-run task splitting is deliberately not here; it is PR 2.

🤖 Generated with Claude Code

choiyounggi and others added 8 commits August 7, 2026 14:59
Wave 배리어를 의존 그래프 + 슬롯 회계로 교체하고, 실행 중 task 분할 경로를 여는 설계.
brainstorming 결과물이며 writing-plans의 입력이 된다.

핵심 결정:
- deps 충족 기준은 approved (impl_done 아님) — 미검토 인터페이스 위에 쌓지 않는다
- 슬롯은 디스패치~종료 상태까지 점유 (리뷰 대기도 포함) — 캡이 보호하는 건 코디네이터 주의력
- 슬롯 수는 코디네이터 제안 + Gate 1 승인, LO_MAX_SESSIONS가 상한
- 분할은 겹치면 같은 워커에 순차 부착, 안 겹치면 새 노드로 병렬
- ready-set.sh exit 3 = 교착 (실패한 의존/사이클) — 조용한 대기와 구분

착지는 PR 2개: 스케줄러 교체, 분할 경로.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
task 4개: ready-set.sh 신규(13 bats), watch-status --tasks 스코프,
Phase 2 재작성(graph.json + 슬롯 제안), Phase 3+4 디스패치 루프 + 재진입.

스펙 §3.3(한도/stall 반응)은 기존 exit 7 경로로 충족되어 새 코드가 없다는 점을
self-review에 근거와 함께 남겼다.
SKILL.md는 한글 0자인 영어 문서인데 계획이 한국어 문단을 넣고 테스트가 한국어를
grep하고 있었다. 배포되는 스킬 본문은 영어가 관례다(guardrails v1.2.0 선례).
docs/ 아래 스펙·계획은 한국어 유지. Global Constraints에 규칙으로 명시.
Wave 배리어 교체의 판정 절반. deps 충족은 approved 이상에서만 성립하고,
리뷰 대기(plan_ready/impl_done)도 슬롯을 점유한다. 실패한 의존으로 인한
교착은 exit 3으로 즉시 드러나며 조용한 대기(exit 2)와 구분된다.
슬롯 스케줄러는 '지금 돌리는 것 중 아무나 하나 도달'이 필요한데, status 디렉토리
전체를 세면 이전 라운드의 approved가 expected=1을 즉시 만족시켜 스핀한다.
argc는 옵션 파싱 뒤에 잡아 4번째 위치 인자 판정이 깨지지 않게 한다.
하드코딩 4를 없애고, 캡이 보호하는 자원(코디네이터 주의력/API 예산)을 보고에
명시하게 한다. LO_MAX_SESSIONS가 상한.
Wave 배리어를 없애고 ready-set 판정 → 빈 슬롯 충전 → 이벤트 대기 루프로 바꾼다.
exit 3(교착)은 대기 금지로 명시했다 — 실행 중인 워커가 없으면 이벤트도 오지 않는다.

- Phase 3: 디스패치 루프로 재구성 (ready-set + watch-status --tasks)
- Phase 4: Wave-return 문장을 dispatch-loop 복귀로 변경
- Re-entry: 중간 상태(Wave index) 불필요 명시
…error handling

FINDING 1: loop now includes step 3 to deliver implement prompt (was missing,
causing hang at plan_ready). Both tmux (send-prompt.sh send) and Orca
(task-create implement Task) paths documented.

FINDING 2: exit codes 3 (DEADLOCK) and 4 (bad read) now explicitly return to
step 1 after human intervention or fix; loop does not stop on errors.

FINDING 3: strengthen contract test to verify loop structure (step 1=ready-set,
step 3=deliver, step 4=watch --tasks, step 5=return to 1), not just keywords.

Plan doc updated to match integrated Phase 3+4 with 5-step loop structure.
@choiyounggi
choiyounggi merged commit fa30c21 into main Aug 7, 2026
2 checks passed
@choiyounggi
choiyounggi deleted the feat/orchestrate-ready-set-scheduler branch August 7, 2026 10:59
choiyounggi added a commit that referenced this pull request Aug 7, 2026
Completes the pair started in #59. A worker that finds its task far larger than the brief assumed can propose splitting it; the coordinator decides with one overlap test and replies either way, because a rejection that is never sent is indistinguishable from silence.

No overlap: a new node via graph-add.sh enters the ready set and the next free slot takes it — real parallelism. Overlap: the piece is added with deps on the parent and handed to the same worker in the same worktree, buying a smaller review unit rather than parallelism. It deliberately does not create a second worktree, since the parent's code is not on the integration branch until Phase 6.

graph-add.sh validates the resulting graph rather than the node alone — duplicate id, undefined dependency, split of a split (depth is capped at 1), an output with two producers, and cycles. Any rejection leaves graph.json byte-identical; a half-applied graph would make re-entry read a state that never existed.

Also syncs both READMEs and bumps the plugin to v1.4.2.

424 tests pass (407 before). Two Criticals were caught in review across the pair, both the same shape: a documented branch that ended with no next action, leaving a worker waiting on a reply nothing told the coordinator to send.

The final CI run also caught a portability bug in this branch's own tests: a `[가-힣]` character range that GNU grep reads as a byte range (matching every em-dash) and that BSD grep matches not at all — green on macOS, red on Linux, and vacuously satisfying its own `== 0` assertion. Replaced with a UTF-8 lead-byte count verified identical on both platforms, plus a negative control so the detector can never again pass by finding nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant