T-FQCN-self-driving-orchestrator-loop
Status: closed/done · Impact: high · Complexity: large
Formerly epic E0001 — retired into a parent Task per D-ORMG-data-model; all member tasks shipped, children link up via
parent_key.
Stand up a hands-off orchestrator that drives the existing SDLC loop end-to-end: pull origin/main,
reconcile worktree/PR state, dispatch ready tasks into /sdlc:task-work sub-agents (parallelism cap
2), shepherd open PRs through review/conflict/CI signals, and run Step 11 close-out the moment a PR
merges. The deliverable boundary is “a single command — /loop /sdlc:orchestrate — keeps the
project moving without manual scheduling for as long as the Claude Code session stays open.” All
member tasks need to land together for that boundary to be real; without pr-check the loop can’t
react to merged/conflicted PRs, and without task-close-out the loop accumulates dead worktrees on
every merge.
The SDLC loop exists but is human-driven at every junction:
- 3 tasks sit in
status: ready(2026-05-17-add-astro-docs-site,2026-05-19-add-update-skill-doc-skill,2026-05-19-document-every-skill-with-mermaid-flowchart); none havereadiness_verified_atstamped — a human has to invoke/sdlc:task-work(or/sdlc:task-ensure-ready) on each. .claude/worktrees/carries 10 active worktrees; at least one (2026-05-19-add-epic-entity-task-depends-on-dependencies) is post-merge but Step 11 close-out never ran, so it showsstatus: in-progressdespite PR #18 being merged. Drift compounds with every merge.- PR triage is fully manual — there’s no skill that says “this PR has a merge conflict / new review comment / failed CI; here’s what to do.” Open PRs #21–#24 are waiting on human attention with no automated signal.
/sdlc:task-work’s Step 11 (markclosed/done, tear down worktree, delete branches) is documented to fire “when the user reports the merge or you notice it later.” There is no callable entry point for it — it’s prose inside a 13-step skill body.- Origin/main sync is ad-hoc. Local main was 5 commits behind origin when this epic was drafted (PRs #17–#20 already merged remotely).
Proposed
Section titled “Proposed”A three-phase rollout, with this epic owning Phase 1:
Phase 1 — Foreground MVP (this epic’s tasks): /sdlc:orchestrate is the tick loop, driven by
/loop /sdlc:orchestrate (self-paced via ScheduleWakeup). Per tick: fetch origin/main and rebase;
classify every open PR via /sdlc:pr-check and dispatch sub-agents to either respond to comments,
rebase to resolve conflicts, or invoke /sdlc:task-close-out for merges; then dispatch up to 2 new
/sdlc:task-work sub-agents on ready tasks (after gating each through /sdlc:task-ensure-ready).
Blockers are not interrupts — in-progress/blocked and proposed/needs-definition statuses
naturally exclude tasks from the dispatch query, so the loop continues. The orchestrator writes a
per-tick digest line to a log file so the user can scan what happened while away. PushNotification
is reserved for state the loop genuinely can’t resolve on its own (e.g., a PR with conflicts the
sub-agent couldn’t resolve, or all ready tasks gated on human-only work). Sub-agents are dispatched
via Agent (not direct Skill invocation) so per-task work stays out of the parent’s context — the
parent only sees one-line return verdicts.
Phase 2 — Cross-session durability (future, out of scope here): Move the tick from /loop
(session-bound) to either CronCreate with durable: true or a dedicated claude --bg session
driven by a Stop-hook re-injection (the /ralph-loop pattern). Both keep the local-git architecture
intact and just remove the “must keep CC open” constraint. Phase 1 deliberately doesn’t pick between
them — we’ll choose after running Phase 1 long enough to see where context rot, idle-timeout, and
reliability actually bite.
Phase 3 — Standalone daemon (future, out of scope here): Extract /sdlc:orchestrate’s logic to
a Python process using the Anthropic Agent SDK. Cron / launchd / systemd drives the cadence.
Per-task work continues to shell out to claude --print "/sdlc:task-work <slug>" so we don’t
rebuild the SDLC skills. Phase 1’s skill body becomes the spec.
Frontmatter tasks: is canonical; this section adds the prose for why each lives here.
- T-3OVF-add-orchestrate-skill — the tick loop itself. Defines reconciliation order (pull → PR
shepherding → ready-task dispatch), parallelism cap (2), blocker policy (skip + log, don’t
notify), and the digest-log convention. Depends on
pr-checkandtask-close-outexisting because it dispatches to both. - T-8896-add-pr-check-skill — single-PR classifier that orchestrate calls per open PR. Returns
one of
{CLEAN, NEEDS-RESPONSE, CONFLICTS, CI-FAILED, MERGED}plus a one-line reason. Tight return contract is the whole point — keeps orchestrator parent context small. - T-RDKI-extract-task-close-out-skill — pulls Step 11 of
/sdlc:task-work(markclosed/done, droprelevance_note, clearreadiness_verified_at, addcompletion_note, tear down worktree, delete branches) into a standalone/sdlc:task-close-outskill. Orchestrator invokes it onpr-check → MERGED;task-workkeeps its existing Step 11 prose but delegates to the new skill so behavior stays in one place.
Acceptance criteria
Section titled “Acceptance criteria”These are outcomes of the bundle, not the sum of member-task ACs:
- AC-E1: After running
/loop /sdlc:orchestrateovernight against the current repo state (10 active worktrees, 4 open PRs, 3 unverified ready tasks), the loop converges: merged-PR worktrees torn down with their tasks closed/done; ready tasks either picked up (PRs opened) or downshifted toproposed/needs-definitionwithdefinition_gaprecorded; no human intervention required for the convergence steps the loop is designed to handle. - AC-E2: Blockers don’t stop the loop. A task that hits
/sdlc:task-workStep 12 (failure →in-progress/blocked) or/sdlc:task-ensure-readyrejection (→proposed/needs-definition) is skipped on the next tick without notification. Blocker reason is captured in the task’s own frontmatter/body (existing<blocked>anddefinition_gapfields) plus a line in the orchestrator’s digest log. - AC-E3: The orchestrator’s parent context grows roughly linearly with ticks, not with work done per tick. Empirically: a tick that dispatches 2 task-work sub-agents adds <2k tokens to the parent transcript (one-line returns + minimal git status output), not the 30-100k of a full task-work run.
- AC-E4: PR shepherding works without the user looking at GitHub. Merge conflicts, new review comments, and CI failures each surface a sub-agent attempt; if the sub-agent can’t resolve, the PR’s state is logged and the loop moves on.
Out of scope
Section titled “Out of scope”- Phase 2 (cross-session durability via
CronCreate durableorclaude --bg). Tasks for that get spawned after Phase 1 has run long enough to inform the choice. - Phase 3 (standalone Python daemon via Agent SDK). Same gating — Phase 1’s skill body is the spec for Phase 3.
- Auto-promotion of
draft/proposed/backlogtasks toready. The orchestrator only dispatches tasks the user has already markedready. Definition and triage remain human (or/sdlc:task-define-assisted) work. - Multi-repo orchestration. Single-repo only for v1.
- Auto-merging PRs. The orchestrator opens PRs and shepherds them through review-readiness; the human merges. Step 11 fires after the human merge.
- A bespoke notification UI.
PushNotification+ the digest log are it for Phase 1.
Discovery context
Section titled “Discovery context”Sparked by the user wanting a “generally hands-off” orchestrator that leverages the Claude Code
harness now and migrates to a standalone application later. Research (see conversation) surveyed
/loop, CronCreate, RemoteTrigger, claude --bg, /ralph-loop, and hooks; the conclusion was
that no single primitive gives “survives CC close + full local tools + can dispatch sub-agents”
simultaneously, so a phased path is the right shape: /loop foreground today, durable session next,
standalone daemon last. The architecture pivots on one insight from the same conversation — invoking
a skill runs in the calling context, but dispatching via Agent isolates that context, so
dispatching task-work through Agent sub-agents (Pattern B) keeps the orchestrator parent context
bounded across many ticks.