T-XRFD-sdlc-p2-orchestrate-loops
State: closed/done · Impact: high · Complexity: large
The orchestrate skill (skills/orchestrate/SKILL.md) is a prose loop today: an
agent session reads instructions and manually drives task dispatch, PR
classification and merge follow-up. Phase 2 replaces the dispatch/reconcile
mechanics with a deterministic CLI tick (sdlc orchestrate run) that composes
existing ops — task next, pr classify, pr update, leases — so the loop is
testable, resumable and safe against double dispatch, with the skill reduced to
a thin --once wrapper.
| Location | Role today |
|---|---|
solutions/ontological/skills/orchestrate/SKILL.md | prose loop being replaced (sync main, classify PRs, dispatch task-work) |
solutions/ontological/lib/services/orchestrator/ops/watch.ts | existing watch op |
solutions/ontological/lib/services/orchestrator/ops/log-tick.ts | existing tick-logging op |
solutions/ontological/lib/services/pr/ops/classify.ts | verdict enum (CLEAN/BEHIND/NEEDS-RESPONSE/CONFLICTS/CI-FAILED/MERGED/CLOSED/ERROR) + per-PR cursor |
solutions/ontological/lib/services/pr/ | pr survey / pr update |
solutions/ontological/lib/services/lease/ | lease task acquire |
task next/inflight ops (lib/services/task/ops/ or equivalent — confirm exact path while implementing) | roadmap-scoped next-ready-task selection, in-flight limits |
Proposed
Section titled “Proposed”sdlc orchestrate run --loop work,prs,merges [--interval <seconds>] [--once] [--dry-run]: a single deterministic tick function per loop name, composed in sequence (work→prs→merges, or whichever subset--loopnames), repeating on--intervalunless--once.- work:
task nextscoped to the roadmap (milestone order), respectingtask inflightlimits; acquire a lease per dispatched task; rundefine(via workflow) if the task isn’t ready, thenimplementviasession launch(configured host or headless perorchestrator:config), then the gate workflow, then update roadmap task state. A task failing repeatedly parks (in-progress/blocked, WIP commit, notify) rather than blocking the tick. - prs:
pr classifyevery open PR. New PRs, or PRs with new commits since the last reviewed commit (tracked on the per-PR cursor frompr classify), getpr-reviewviaorchestrator.review.engine(a different engine/model from the PR author’s, when known).NEEDS-RESPONSEandCI-FAILEDverdicts dispatchpr-respond. Round-tripping stops atorchestrator.review.max_rounds; hitting the limit sends a notification instead of looping forever. - merges: on a
MERGEDverdict, runclose-out, refresh roadmap state, thenpr updateacross the remaining open PRs and stacked branches.CONFLICTSverdicts route topr-respond. A PR whose base moved gets re-queued for review. - Leases: every dispatched item (task or PR action) holds a lease for its duration, so two overlapping ticks (or a tick racing a manual invocation) cannot double-dispatch the same item. Lease acquisition failure skips the item for this tick rather than erroring the whole run.
- Logging: every tick appends one entry via
orchestrate log-tick(reuse, don’t reimplement). --dry-run: prints the planned actions (what would be dispatched, on what, and why) and performs no side effects — no lease acquisition, no session launch, no git/gh writes.- Minimal notifier:
notify.desktop(osascript on macOS / notify-send on Linux) andnotify.ntfy(HTTP POST to a configured topic URL), used for parks and max-rounds. A thin seam Phase 8 grows into the fullnotify:section — keep the interface (notify(event, payload)) stable rather than feature-complete. orchestrator:config section inSdlcConfigSchema: loop enable flags, tick interval, host/engine per workflow,review.engine,review.max_rounds, PR filters (e.g. label/author excludes).skills/orchestrate/SKILL.mdbecomes a thin wrapper invokingsdlc orchestrate run --once; any plugin-relative path in it uses${CLAUDE_PLUGIN_ROOT}/...(with the slash) per the corpus-invocation gate.
Approach
Section titled “Approach”- Confirm the exact current paths/signatures for
task next,task inflight,pr classify,pr survey/pr update,lease task acquire,orchestrate log-tickby reading each module — do not guess signatures. - Design the
orchestrator:config section and add it toSdlcConfigSchema. - Implement the notifier seam (
lib/services/notify/—desktop.ts,ntfy.ts, annotify(event, payload)dispatcher readingorchestrator/notifyconfig) first, sincework/prsboth need it for parks/max-rounds. - Implement the
worktick: task selection → lease → define (if needed) → implement (viasession launch) → gate → roadmap update → park-on-repeat-failure. - Implement the
prstick: classify-all → new/changed-commit detection off the cursor →pr-reviewdispatch →NEEDS-RESPONSE/CI-FAILED→pr-respond→ round counting → max-rounds notify. - Implement the
mergestick:MERGED→ close-out → roadmap refresh →pr updatefan-out →CONFLICTS→pr-respond→ re-queue on base move. - Implement
--dry-runas a planning-only code path shared by all three ticks (same selection logic, no dispatch), and--interval/--onceas the outer loop driver. - Rewrite
skills/orchestrate/SKILL.mdas the thin wrapper. - Tests per T-REME: dry-run planning against fixture PRs/roadmap/tasks for each loop, and a lease double-dispatch test (two concurrent tick invocations racing the same task/PR resolve to exactly one dispatch).
| Area | Note |
|---|---|
solutions/ontological/lib/config/load.ts | add orchestrator: section to SdlcConfigSchema |
solutions/ontological/lib/services/orchestrator/ops/run.ts | orchestrate run op: loop composition, --interval/--once/--dry-run |
solutions/ontological/lib/services/orchestrator/ticks/work.ts | work loop tick |
solutions/ontological/lib/services/orchestrator/ticks/prs.ts | prs loop tick |
solutions/ontological/lib/services/orchestrator/ticks/merges.ts | merges loop tick |
solutions/ontological/lib/services/notify/dispatch.ts | notify(event, payload) seam |
solutions/ontological/lib/services/notify/desktop.ts | osascript / notify-send channel |
solutions/ontological/lib/services/notify/ntfy.ts | ntfy HTTP POST channel |
solutions/ontological/lib/registry.ts | register orchestrate run |
solutions/ontological/skills/orchestrate/SKILL.md | thin --once wrapper |
solutions/ontological/lib/services/orchestrator/ticks/*.test.ts | dry-run planning tests against fixtures |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
sdlc orchestrate run --loop work,prs,merges --dry-run --onceagainst fixture PRs, a fixture roadmap and fixture tasks prints a correct plan and performs zero writes (no lease files, no git/gh calls, no session launches). - AC-2: a
prs-loop test proves aMERGEDverdict routes to close-out +pr updatefan-out,CONFLICTSroutes topr-respond, andNEEDS-RESPONSE/CI-FAILEDroute topr-respond. - AC-3: a review round-tripping test proves the loop stops dispatching after
orchestrator.review.max_roundsand fires a notification instead. - AC-4: a lease double-dispatch test proves two concurrent
work-tick invocations against the same ready task result in exactly one lease holder and one dispatch. - AC-5:
skills/orchestrate/SKILL.mdcontains no logic beyond invokingsdlc orchestrate run --once, and any plugin path in it is written${CLAUDE_PLUGIN_ROOT}/.... - AC-6:
notify.desktopandnotify.ntfyeach fire on a park and on hittingmax_rounds, verified by a test doubling the underlying command/HTTP call.
Out of scope
Section titled “Out of scope”- Running the loops live against GitHub — the top orchestrator decides when to
run them for real; this phase only ships
--dry-run-safe code plus tests against fixtures. - The full
notify:config section (channels beyond desktop/ntfy, event catalog) — Phase 8. - New hosts (claude/codex/cursor) — Phase 6.
- The SDLC chain / label-driven autonomy / issue loop — Phase 7.
Dependencies
Section titled “Dependencies”[T-A7PL-sdlc-p2-workflows](/planning/tasks/sdlc-p2-workflows/)— every tick step (implement,check,judge,pr-review,pr-respond,pr-update,close-out,define) is a workflow name resolved through T-A7PL’sresolveWorkflow.[T-AD81-sdlc-p2-session-launch](/planning/tasks/sdlc-p2-session-launch/)—implement/define/pr-review/pr-responddispatch throughsession launch.
Discovery context
Section titled “Discovery context”Phase 2 of docs/planning/notes/N-S4AJ-sdlc-0-8-plan.md (“sdlc orchestrate run —loop
work,prs,merges”).
Depends on
Section titled “Depends on”T-A7PL-sdlc-p2-workflows, T-AD81-sdlc-p2-session-launch