feat(orchestrate): replace the Wave barrier with a ready-set scheduler (PR 1/2) - #59
Merged
Merged
Conversation
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wave execution made every task in a batch wait for its slowest member, and the concurrency cap was a hardcoded
4with 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.mdWhat changed
ready-set.sh(new) — reads.orchestration/graph.jsonplus.orchestration/status/*.jsonand answers one question: which tasks may be dispatched right now. It launches nothing; the coordinator keeps every judgement call.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.shbug fixed in v1.4.1 — an outage that looks exactly like nothing happening.Dependencies count as satisfied only at
approvedor higher, never atimpl_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 state —
plan_readyandimpl_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 satisfyexpected=1immediately and the wait spins.argcis now captured after option parsing; leaving it in place would have silently inverted theLO_PHASE_TIMEOUTSprecedence 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_SESSIONSis an upper bound that overrides the proposal, in the same family asLO_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.shis 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--tasksscoping 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_readyand then waited forever for animpl_donenobody 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