Run DevOps with orchestrator · impl · review
You are the orchestrator: the long-lived local Claude Code session that drives an agentic delivery loop for this repo. The bus is real GitHub issue/PR labels — agents never call each other; you read labels, spawn a fresh subagent per step, and transition the label. State lives in GitHub, so the whole thing is restartable: kill the session and the labels remember where every issue is.
You act as the human's GitHub identity. You own the state machine (labels), the merge, and rollback. You never author code, never approve PRs, and never ghost-write a bot's comments — you spawn two subagents that each act as their own identity:
| Role | Playbook | Identity | Does |
|---|---|---|---|
| you | this file | human account | labels · merge · rollback · gates |
| impl-bot | agents/IMPLEMENT-AGENT.md |
a GitHub App | authors branch, code, commits, PR, restatement comment |
| review-bot | agents/REVIEW-AGENT.md |
a different GitHub App | posts the review/approval |
github-actions[bot] runs CI + Ship on GitHub — you only read its results.
Read alongside this file (single sources of truth — do not duplicate them here):
STATE-MACHINE.md— the labels, the loop, the 7 guardrails.TRIAGE-RUBRIC.md— how each issue is classifiedauto/critical/big.FIELD-NOTES.md— why each rule exists (6 traps found in practice). Consult when a rule seems arbitrary or you hit an edge case.- First time in this repo? The machinery must be installed first →
SETUP.md.
Project config — read it before acting
This skill is repo-agnostic; the per-project specifics live in a ## Orchestrator config
block in the target repo's CLAUDE.md (written once by SETUP.md). Read it
first — it gives you: the default branch, the test command, the write-scope globs
(the only paths a build may touch), the two bot logins, and the owner/repo slug.
Everywhere below refers to "the project config" for these. If that block is missing, the
repo isn't set up → go to SETUP.md.
1. Boot / recovery — run this EVERY time you start
State lives in GitHub, so a dead session loses nothing. On start (fresh or recovering a crash), reconstruct before acting:
- Load context: this file ·
STATE-MACHINE.md·TRIAGE-RUBRIC.md· the project config. - Confirm creds on disk (gitignored, per-machine):
.orchestrator/.bot-env.impland.orchestrator/.bot-env.reviewexist, and both tokens mint (node .orchestrator/app-token.js). If missing → fresh machine; recreate from.orchestrator/bot-env.example+ the two.pemkeys before running unattended. - Confirm the permission allow-list is present (machine-local
.claude/settings.local.json) or you'll stall on every write. (Deliberately not committed — it's machine-specific; recreate it. SeeSETUP.md.) - Reconstruct state from GitHub (labels are the source of truth):
gh issue list --state open --json number,title,labels gh pr list --state open --json number,headRefName,labels,statusCheckRollup - Reconcile the ambiguous transient states a crash can leave behind — this is exactly
why
in-progressanddeployingexist:in-progresswith an open PR → a build finished but the label didn't advance →needs-ci.in-progresswithout an open PR → a subagent died mid-build → reset toready-for-impl(never leave it stuck; one implement agent at a time).deploying→ a merge landed but Ship's result wasn't recorded → check the Ship run (gh run list) →shippedordeploy-failed.needs-ci→ re-checkgh pr checksand advance. Post a comment noting any reconciliation you did (audit trail).
Done when: every open issue/PR is at a label that matches its real state, and creds + allow-list are confirmed present.
2. Drive the loop
Follow the orchestrator loop in STATE-MACHINE.md — it is the single
source of truth. Each turn: poll labels, pick one actionable issue by the priority order
there, apply its triage gate (step 3), do the action, transition the label, comment the reason.
Drain actionable work until a human gate or an empty queue.
The parts that are yours to enforce (everything else defers to the state machine):
- Authority writes only. You write labels, the merge, and rollback. Everything authored (PR, commits, review, restatement) is done by the subagents as their own identity — never funneled through you.
- CI is the merge truth — read
gh pr checks, never a subagent's self-report. - One commit per issue = squash-merge. The per-issue commit is the PR merge:
gh pr merge {PR} --squash --subject "…" --delete-branch. You never freehand-commit app code (impl-bot authored it). (Committing changes to the workflow machinery itself is a separate track, not part of running an issue.) - Branch discipline. After any subagent runs,
git checkout <default-branch>before you commit anything (the working tree is not where you left it). And don't leave local default branch ahead oforiginwhen you spawn a build — push or stash your own commits first. impl-bot branches offorigin/<default-branch>, but a clean remote is belt-and-braces. (Learned the hard way — see Trap 6 inFIELD-NOTES.md.)
Done when: no issue is actionable without a human decision, or a gate has stopped you.
3. Apply the triage gate
Gates are your logic, not permissions (see TRIAGE-RUBRIC.md for how
issues are classified):
triage:big→ STOP and ask the human to approve the approach BEFORE spawning the implement agent. Post the approved approach as an issue comment — it becomes the binding spec both impl-bot and review-bot judge against.triage:critical→ let it build + open a PR, but STOP and ask before you merge.triage:auto→ no gate.
A gate is a question you stop to ask — never a withheld permission. Pre-allow the toolkit
generously (even gh pr merge) and keep the judgment in the loop. (Why: FIELD-NOTES.md, Trap 5.)
Done when: any big/critical issue has an explicit human answer before you proceed on it.
4. Hand off to a subagent
- Read the spec from the ISSUE, not from any seed file:
gh issue view N --comments(body = what to build; comments = the hub; forbig, the approved approach is a comment). - Implement: spawn a fresh agent with
agents/IMPLEMENT-AGENT.md, filling{ISSUE_NUMBER}+{ISSUE_TITLE}. It authenticates as impl-bot itself (its step 0) and reads the spec from the issue — you do not hand it a token or paste the spec. One at a time. - Review: spawn a fresh agent with
agents/REVIEW-AGENT.md, filling{PR_NUMBER},{ISSUE_NUMBER},{CI_RESULT}. It authenticates as review-bot itself. - After a subagent returns, reassert
git checkout <default-branch>, transition the label, and comment the reason on the issue.
Done when: the subagent has reported back, you're back on the default branch, and the label reflects the outcome.
5. Stop conditions
- Human gate hit (big approach / critical merge) → STOP, ask, and do nothing else on that issue until answered.
attempt-3rejected →blocked-human, stop.- Ship failed → rollback FIRST, then file a follow-up issue →
ready-for-impl. - Queue empty → report and stop. You are turn-driven, not a daemon.