Skip to content

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.

LocationRole today
solutions/ontological/skills/orchestrate/SKILL.mdprose loop being replaced (sync main, classify PRs, dispatch task-work)
solutions/ontological/lib/services/orchestrator/ops/watch.tsexisting watch op
solutions/ontological/lib/services/orchestrator/ops/log-tick.tsexisting tick-logging op
solutions/ontological/lib/services/pr/ops/classify.tsverdict 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
  • 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 --loop names), repeating on --interval unless --once.
  • work: task next scoped to the roadmap (milestone order), respecting task inflight limits; acquire a lease per dispatched task; run define (via workflow) if the task isn’t ready, then implement via session launch (configured host or headless per orchestrator: 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 classify every open PR. New PRs, or PRs with new commits since the last reviewed commit (tracked on the per-PR cursor from pr classify), get pr-review via orchestrator.review.engine (a different engine/model from the PR author’s, when known). NEEDS-RESPONSE and CI-FAILED verdicts dispatch pr-respond. Round-tripping stops at orchestrator.review.max_rounds; hitting the limit sends a notification instead of looping forever.
  • merges: on a MERGED verdict, run close-out, refresh roadmap state, then pr update across the remaining open PRs and stacked branches. CONFLICTS verdicts route to pr-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) and notify.ntfy (HTTP POST to a configured topic URL), used for parks and max-rounds. A thin seam Phase 8 grows into the full notify: section — keep the interface (notify(event, payload)) stable rather than feature-complete.
  • orchestrator: config section in SdlcConfigSchema: loop enable flags, tick interval, host/engine per workflow, review.engine, review.max_rounds, PR filters (e.g. label/author excludes).
  • skills/orchestrate/SKILL.md becomes a thin wrapper invoking sdlc orchestrate run --once; any plugin-relative path in it uses ${CLAUDE_PLUGIN_ROOT}/... (with the slash) per the corpus-invocation gate.
  1. Confirm the exact current paths/signatures for task next, task inflight, pr classify, pr survey/pr update, lease task acquire, orchestrate log-tick by reading each module — do not guess signatures.
  2. Design the orchestrator: config section and add it to SdlcConfigSchema.
  3. Implement the notifier seam (lib/services/notify/ — desktop.ts, ntfy.ts, an notify(event, payload) dispatcher reading orchestrator/notify config) first, since work/prs both need it for parks/max-rounds.
  4. Implement the work tick: task selection → lease → define (if needed) → implement (via session launch) → gate → roadmap update → park-on-repeat-failure.
  5. Implement the prs tick: classify-all → new/changed-commit detection off the cursor → pr-review dispatch → NEEDS-RESPONSE/CI-FAILED → pr-respond → round counting → max-rounds notify.
  6. Implement the merges tick: MERGED → close-out → roadmap refresh → pr update fan-out → CONFLICTS → pr-respond → re-queue on base move.
  7. Implement --dry-run as a planning-only code path shared by all three ticks (same selection logic, no dispatch), and --interval/--once as the outer loop driver.
  8. Rewrite skills/orchestrate/SKILL.md as the thin wrapper.
  9. 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).
AreaNote
solutions/ontological/lib/config/load.tsadd orchestrator: section to SdlcConfigSchema
solutions/ontological/lib/services/orchestrator/ops/run.tsorchestrate run op: loop composition, --interval/--once/--dry-run
solutions/ontological/lib/services/orchestrator/ticks/work.tswork loop tick
solutions/ontological/lib/services/orchestrator/ticks/prs.tsprs loop tick
solutions/ontological/lib/services/orchestrator/ticks/merges.tsmerges loop tick
solutions/ontological/lib/services/notify/dispatch.tsnotify(event, payload) seam
solutions/ontological/lib/services/notify/desktop.tsosascript / notify-send channel
solutions/ontological/lib/services/notify/ntfy.tsntfy HTTP POST channel
solutions/ontological/lib/registry.tsregister orchestrate run
solutions/ontological/skills/orchestrate/SKILL.mdthin --once wrapper
solutions/ontological/lib/services/orchestrator/ticks/*.test.tsdry-run planning tests against fixtures
  • AC-1: sdlc orchestrate run --loop work,prs,merges --dry-run --once against 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 a MERGED verdict routes to close-out + pr update fan-out, CONFLICTS routes to pr-respond, and NEEDS-RESPONSE/ CI-FAILED route to pr-respond.
  • AC-3: a review round-tripping test proves the loop stops dispatching after orchestrator.review.max_rounds and 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.md contains no logic beyond invoking sdlc orchestrate run --once, and any plugin path in it is written ${CLAUDE_PLUGIN_ROOT}/....
  • AC-6: notify.desktop and notify.ntfy each fire on a park and on hitting max_rounds, verified by a test doubling the underlying command/HTTP call.
  • 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.
  • [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’s resolveWorkflow.
  • [T-AD81-sdlc-p2-session-launch](/planning/tasks/sdlc-p2-session-launch/) — implement/define/pr-review/ pr-respond dispatch through session launch.

Phase 2 of docs/planning/notes/N-S4AJ-sdlc-0-8-plan.md (“sdlc orchestrate run —loop work,prs,merges”).

T-A7PL-sdlc-p2-workflows, T-AD81-sdlc-p2-session-launch


← Back to Tasks