T-3OVF-add-orchestrate-skill
Status: closed/done · Impact: high · Complexity: medium
Ship /sdlc:orchestrate — one skill that, when invoked, performs a single reconcile-and-dispatch
tick over the project’s task/PR/worktree state. Combined with /loop /sdlc:orchestrate, this is the
Phase 1 entry point for the T-FQCN-self-driving-orchestrator-loop epic: hands-off task pickup
and PR shepherding inside a Claude Code session.
The orchestration work is fully manual. To move a task through the SDLC loop today, the user has to:
-
Read
git fetch origin && git log origin/main..HEAD --onelineand decide whether to rebase. -
Eyeball
git worktree listand cross-reference each worktree againstgh pr listto see what’s merged, conflicted, or waiting on review. -
Pick a ready task from
docs/planning/tasks/(currently 3 sit instatus: readywith noreadiness_verified_atstamp), invoke/sdlc:task-ensure-readyon it, then/sdlc:task-work. -
After a PR merges, manually run Step 11 of
/sdlc:task-work(markclosed/done, tear down worktree, delete branches). The 10 active worktrees include at least one (2026-05-19-add-epic-entity-task-depends-on-dependencies) where Step 11 never ran despite PR#18 being merged.
There is no skill that performs this orchestration loop, no convention for where blocker outcomes get logged across ticks, and no place that records what the orchestrator did between user check-ins.
Proposed
Section titled “Proposed”A skill at plugin/skills/orchestrate/SKILL.md (invocable as /sdlc:orchestrate) that performs one
tick:
- Sync:
git fetch origin && git pull --rebase --autostashon main. Abort the tick with a logged reason if the rebase has conflicts. - Reconcile open PRs: list open PRs via
gh pr list. For each, dispatch anAgent(general-purpose)sub-agent that invokes/sdlc:pr-check <pr-number>and returns one of{CLEAN, NEEDS-RESPONSE, CONFLICTS, CI-FAILED, MERGED}plus a one-line reason. Parent dispatches on the verdict:MERGED→ spawn sub-agent running/sdlc:task-close-out <task-slug>.NEEDS-RESPONSE/CONFLICTS/CI-FAILED→ spawn sub-agent in the worktree to attempt resolution (respond to comment, rebase, fix CI). If it can’t resolve, log and move on.CLEAN→ no-op.
- Dispatch ready tasks: query
status: readyANDautonomy != human-onlyAND no existing worktree/branch. Cap dispatch at 2 concurrent task-work runs (parent counts active sub-agents). For each picked task, spawnAgent(general-purpose)running/sdlc:task-work <slug>with a tight return contract:DONE pr=#N | BLOCKED reason=... | NEEDS-DEFINITION slug=.... - Digest: append one Markdown line per tick to
.claude/orchestrator-log.md(gitignored) summarizing: timestamp, PR verdicts, dispatched tasks, blockers skipped. This is the “what did the orchestrator do while I was away” surface. - Notify:
PushNotificationonly when state can’t progress without a human — e.g.,gh pr listis empty AND there are zero LLM-pickable ready tasks AND there are tasks inproposed/needs-definition. Routine blockers (in-progress/blocked, a single failed sub-agent) do not notify. - Return to caller. When wrapped in
/loop, the model usesScheduleWakeupto fire the next tick (default ~20 min idle, ~5 min when work is in flight).
Sub-agents are always dispatched via Agent (never direct Skill calls from the parent body) so
per-task chatter stays out of the orchestrator’s transcript. Parent only sees one-line return
verdicts.
Approach
Section titled “Approach”- Scaffold
plugin/skills/orchestrate/SKILL.mdwith frontmatter (description,allowed-tools: [Bash, Read, Agent, PushNotification]). - Write the tick procedure prose, mirroring the structure of
/sdlc:task-work(numbered steps, explicit failure modes, what the skill MUST/MUST NOT do). - Encode the parallelism cap (2) as a literal in the skill body — no config file yet; revisit in Phase 2.
- Add the digest-log convention: path
.claude/orchestrator-log.md, format<ISO timestamp> tick=<N> prs=<verdicts> tasks-dispatched=<slugs> blocked=<slugs>. Add.claude/orchestrator-log.mdto.gitignore(or.claude/already is — confirm). - Add
plugin/skills/orchestrate/invariants.yamlper the project’s skill-prose lint convention (mirror whattask-workandimport-planningship). Invariants worth encoding: “parent must dispatch task-work via Agent, never via direct Skill”; “parallelism cap of 2”; “blockers are skipped, not notified”. - Write a short README section pointing to
/loop /sdlc:orchestrateas the recommended invocation. - Dry-run by hand: invoke
/sdlc:orchestrateonce against the current repo state and walk through what it would do (or actually do, withgh pr listand worktree reconciliation live). Iterate if the tick takes the wrong action.
Files to touch
Section titled “Files to touch”plugin/skills/orchestrate/SKILL.md(new) — the tick procedure.plugin/skills/orchestrate/invariants.yaml(new) — lint invariants for the skill prose..gitignore— confirm.claude/orchestrator-log.mdis ignored (likely already covered by.claude/).docs/planning/epics/E0001.md— no edit; this task is already listed under the epic.
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
/sdlc:orchestrateinvocable; runs a single tick end-to-end against the live repo (10 worktrees, 4 open PRs, 3 unverified ready tasks) and produces a digest-log line plus reasonable dispatch decisions. (agent-manual) - AC-2: Tick never dispatches more than 2 concurrent
/sdlc:task-worksub-agents. Verifiable by reading the SKILL.md and the invariants file. (auto) - AC-3: Tasks with
status: in-progress/blockedorstatus: proposed/needs-definitionare silently skipped on the next tick — noPushNotificationfires for them. (agent-manual: set a fixture task to each state, confirm) - AC-4: Sub-agent dispatch uses
Agent(general-purpose)for task-work and pr-check (verifiable in SKILL.md prose) — NOT directSkillinvocation. The orchestrator parent transcript per tick grows by <2k tokens excluding sub-agent return strings. (auto via lint invariant + agent-manual measurement) - AC-5:
lint_skill_prose.pypasses for the new SKILL.md against its invariants.yaml. (auto) - AC-6: After dry-run against current repo state, at least one of: a stale worktree torn down, a ready task picked up, or a PR triaged. The orchestrator demonstrably moves state forward. (agent-manual)
Out of scope
Section titled “Out of scope”pr-checkandtask-close-outskills (separate tasks in this epic).- Cross-session durability (Phase 2 — see T-FQCN-self-driving-orchestrator-loop Out of scope).
- Auto-merging PRs or auto-promoting tasks from
draft/proposedtoready. - Resolving the existing worktree mess. The orchestrator should clean it up naturally on its first few ticks; if it can’t, file a follow-up.
Dependencies
Section titled “Dependencies”- T-8896-add-pr-check-skill — orchestrate calls it per open PR.
- T-RDKI-extract-task-close-out-skill — orchestrate calls it when pr-check returns
MERGED.
Both must land first or in the same PR train; orchestrate can’t dry-run without them.
Discovery context
Section titled “Discovery context”Phase 1 of epic T-FQCN-self-driving-orchestrator-loop. Designed in conversation 2026-05-19 — see
epic Discovery context for the research that informed the Agent-dispatch-not-direct-Skill choice
that this skill encodes.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-20. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”The AC-1 parenthetical state (“10 worktrees, 4 open PRs, 3 unverified ready tasks”) was stale by the
time this run started — most of the worktree backlog had already been reconciled and the PR queue is
empty. The AC body itself says “the live repo” so it self-heals; the dry-run was traced against
current state (0 open PRs, 3 open/ready tasks, 1 in-flight worktree — this one).
- AC-1: agent-manual —
plugin/skills/orchestrate/SKILL.mdexists and is structurally complete (frontmatter + 6 numbered steps + Failure modes + Notes). Dry-run trace against live state: Step 1git fetch originexits clean, Step 2 finds zero open PRs (so no pr-check dispatches), Step 3 scans forstatus: open/readyand finds three (2026-05-17-add-astro-docs-site,2026-05-19-task-ensure-ready-accepts-in-progress,2026-05-19-templates-html-comment-after-frontmatter) with one in-flight worktree — at cap=2, would dispatch one more (astro-docs-site, oldest). Digest line would be<ts> tick=1 sync=ok prs=<empty> tasks-dispatched=2026-05-17-add-astro-docs-site blocked=<none>. - AC-2: auto —
invariants.yamlrequired_phrasesincludes"parallelism cap of 2"pinned to the “Parallelism cap” section;lint_skill_prose.pyasserts the literal appears. - AC-3: agent-manual — Step 5 prose explicitly enumerates the three notify-only conditions (no PRs +
no ready tasks + ≥1 needs-definition/blocked) and the “routine blockers do NOT notify” carve-out.
Invariants pin both
"only when genuinely stuck"and"Routine blockers do NOT notify"as required phrases. A fixture task inplanning/needs-definitionwould land in the digest’sblocked=<slug>field, not trigger PushNotification, as long as any other condition fails. - AC-4: auto — invariants
forbidden_phrasesblock direct-Skill dispatch from the parent body (Skill(/sdlc:pr-check,Skill(/sdlc:task-close-out,Skill(/sdlc:task-workare all absent). Required phrase"Agent(general-purpose)"is present under the “Dispatch contract” section. The <2k-token transcript-growth target in the AC body is a measurement claim that needs live-tick verification; spec-only at this point. - AC-5: auto —
lint_skill_prose.py plugin/skills/*/SKILL.mdexits 0 across 19 skills with 7 invariants files. - AC-6: agent-manual — same dry-run trace as AC-1: tick would dispatch
/sdlc:task-work 2026-05-17-add-astro-docs-site, satisfying the “ready task picked up” branch of AC-6’s disjunction (the other two — stale worktree torn down, PR triaged — don’t fire because state is already clean). State-forward property confirmed.
What worked
Section titled “What worked”- The
forbidden_phrasesmechanism made the Agent-dispatch contract enforceable as lint: any future drift toward direct-Skill dispatch from the parent body trips immediately. This is exactly the kind of contract that previously only held by reviewer attention. - The
required_phraseswithsection:hints pinned the parallelism cap and the dispatch surface to specific H2/H3 sections, so the linter knows where each invariant must appear (not just “somewhere in the file”). One required phrase trip caught a wording drift on the first lint run, before commit. - Following the same SKILL.md shape as
pr-checkandtask-close-out(description + allowed-tools frontmatter, numbered steps, Failure modes, Notes) made the orchestrate body slot into the project’s existing convention without bespoke scaffolding.
Friction and automation gaps
Section titled “Friction and automation gaps”- AC-1’s parenthetical state (“10 worktrees, 4 open PRs, 3 unverified ready tasks”) had drifted to
(1, 0, 3) by the time this task was picked up. The AC body self-heals via “the live repo” wording,
but the parenthetical is misleading on read. Same general gap as the close-out task — a
/sdlc:task-review-level relevance check on AC parenthetical state would catch this. Already tracked by T-IXV6-task-review-checks-ac-fixture-relevance (spawned from the previous task’s post-mortem). → T-IXV6-task-review-checks-ac-fixture-relevance - AC-4’s “parent transcript per tick grows by <2k tokens excluding sub-agent return strings” is a measurement claim that can’t be verified without running a real tick end-to-end and measuring. Spec-only today; needs a token-counting fixture once /loop /sdlc:orchestrate runs against the live repo for a few ticks. → T-BLYF-orchestrator-tick-token-budget-fixture
- The skill has not been exercised live yet — AC-1 / AC-3 / AC-6 are all dry-run traces, not actual ticks. A first /reload-plugins + /loop /sdlc:orchestrate run would surface any prose-vs-implementation gaps. The hard parts (Agent dispatch shape, pr-check return parsing, task-work return parsing) are spec-only until then. → T-JWCO-orchestrate-first-live-tick-shakedown
- The cross-skill invariant “parent transcript stays small” depends on sub-agents returning ONE LINE only. The current prompts say “return one verdict line” but the model has no hard cap; a sub-agent that monologues would violate the contract silently. Worth a follow-up — either tighten the prompt phrasing further or add a parent-side guard that truncates verbose returns. → T-TJB3-orchestrate-enforces-one-line-verdict
- The “1 in-flight task counts toward the cap” rule is detected by checking for
.claude/worktrees/<basename>+ matchingfeat/<basename>branch. If a worktree exists but the task file’sstatusis alreadyclosed/done(stale worktree, didn’t tear down), the orchestrator would over-count and under-dispatch by one. Minor; the orchestrator’s own close-out passes will clean these up over time, but a sanity check that worktrees correspond to in-progress tasks would be cleaner. →2026-05-20-orchestrate-validates-worktree-task-status
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-IXV6-task-review-checks-ac-fixture-relevance —
task-reviewaudits AC parenthetical fixture state (linked existing) - T-BLYF-orchestrator-tick-token-budget-fixture — instrument per-tick parent-transcript token growth and assert AC-4’s <2k cap (created)
- T-JWCO-orchestrate-first-live-tick-shakedown — run
/loop /sdlc:orchestratelive to surface prose-vs-implementation gaps (created) - T-TJB3-orchestrate-enforces-one-line-verdict — tighten one-line return contract via prompt + parent-side truncation guard (created)
2026-05-20-orchestrate-validates-worktree-task-status— orchestrate sanity-checks worktree↔task-status correspondence before counting cap (created)
Depends on
Section titled “Depends on”T-8896-add-pr-check-skill, T-RDKI-extract-task-close-out-skill