Forge owns your open PRs. A singleton daemon converges every open PR toward merge-readiness; you read its verdicts and only step in on escalations. Never merges, never resolves review threads.
Shepherd
shepherd is the PR-ownership utility, not a workflow stage. It has two modes over the single forge shepherd verb:
- Daemon (default ownership):
forge shepherd daemon is a machine-wide singleton for this repo. It heartbeats and converges the whole PR world every ~60s — self-registering hand-opened PRs, restarting killed watchers, reaping verified orphans, converging CI check state into kernel verdicts, retiring merged/closed PRs — then self-retires (releases the lease, kills its children, exits) once no PRs remain open. You do NOT poll by hand; the daemon does.
- One-shot convergence:
forge shepherd <pr> runs the local review preflight, reads one PR's current-head state, takes at most one idempotent Tier-A action, persists bounded deltas/receipts, and exits. Use it for a point-in-time question or one bounded convergence attempt on a single PR.
Which mode? (decision rule)
- User asks about one PR, right now ("is #212 ready?", "why is this blocked?") → one-shot (
forge shepherd <pr> --pull).
- Anything ongoing — "keep", "until it merges", "watch", "babysit", multiple PRs, or a session that starts with PRs already open → ensure the daemon is running.
Automatic attachment
Forge wakes the singleton after a successful supported session start, every
successful push, and every successful non-dry-run ship. Push does not require a
locally resolved PR number; the daemon enumerates all open and draft PRs itself.
Ordinary commands do not launch it.
The daemon is a repo singleton guarded by an O_EXCL lease, so no liveness
check is needed first. Forge prefers a supplied harness background shell; its
bare-CLI detached fallback launches from the stable common repository root, not
the current disposable worktree. Agents never launch a detached spawn directly.
(A forge prime daemon-liveness line remains the separate W-S5 follow-up.)
Reading verdicts (the common case)
forge shepherd <pr> --pull --json # actionable payload: WHY blocked + exactly what to fix
forge shepherd <pr> --bundle --json # the COMPLETE read-only PR-state bundle
forge shepherd events <pr> --since <seq> # only the new events since sequence <seq>
--pull is strictly read-only (dry-run pass: no rerun, no rebase, no merge, no thread resolution). It returns one bounded, actionable-only payload — blockers[], classified requiredChecks, failed-check log failures[] (matrix-deduped), and every unresolved reviewThreads[] — so you get "everything blocking this PR + what to fix" in one call. Passing checks and satisfied policy are omitted.
A plain forge shepherd <pr> also returns a consolidated localPreflight, at
most 128 deltas, receiptIds, and one explicit handoff. The preflight probes
CodeRabbit and runs strict lint, Sonar parity, structural drift, and affected
tests. An unavailable or unauthenticated CodeRabbit CLI is reported as
UNAVAILABLE/INCOMPLETE, never as a fabricated pass; deterministic failures or
actual review findings make the remote decision read-only for that pass. A
checkout that is not the exact PR head is NOT_APPLICABLE, never reviewed as if
it were authoritative.
Verdict vocabulary (collapsed, W-S1)
| Verdict |
Meaning |
MERGE_READY |
Required checks green, branch up to date — hand off to a human to merge. |
PENDING |
A Tier-A action was taken, or checks are still running — await the next tick/pass. |
INCOMPLETE |
Exact-head local review or durable convergence evidence is unavailable — fail closed and retry after evidence is restored. |
BLOCKED |
Something actionable blocks merge (failing/missing/skipped required check, conflict, behind, unresolved threads, changes requested). Read blockers[]. |
CI_DEAD_HEAD |
The head has no required checks running (e.g. an auto-update authored by GITHUB_TOKEN never re-triggered CI). Recovery is an escalation, not an autonomous Tier-A rerun: it needs a maintainer-provided FORGE_PR_TOKEN (contents + pull-requests + checks) to re-author the push so CI re-triggers. |
ESCALATE |
A Tier-C condition (conflict, unreadable required set, persistent failure, oscillation, budget exhaustion). Context is posted to the PR. |
HARD_STOP |
A permanent auth/scope failure retrying cannot fix — a human must widen token scope. |
Trigger scenario → command
| Situation |
Command |
| PR just opened / shipped |
automatic singleton wake; read with forge shepherd <pr> --pull |
| Session starts with open PRs |
automatic singleton wake |
| "Why isn't my PR merging / what's blocking it" |
forge shepherd <pr> --pull |
| "Is the PR ready?" |
forge shepherd <pr> --pull (read MERGE_READY) |
| "A check failed / went red" |
forge shepherd <pr> --pull --json (read failures[]) |
| "Keep watching / babysit my PRs" |
ensure forge shepherd daemon running |
| Read incremental deltas |
forge shepherd events <pr> --since <seq> |
Boundaries (kept — true of both modes)
- Never merges. No merge action, no server-side auto-merge latch. Terminates at
MERGE_READY and hands off — a human merges in the GitHub UI.
- Never resolves review threads. It refreshes a single sticky status comment; thread resolution is semantic and stays with
review.
- Action ladder. Tier-A (autonomous, idempotent): re-run a flaky required check (rerun-budget capped); refresh the single sticky status comment — an upsert, never an append, so the ~60s daemon loop cannot post duplicate comments. Tier-B (opt-in, default OFF):
--auto-rebase rebases onto base and force-pushes with lease — a lease rejection is a hard-stop, never re-armed. Tier-C: everything else escalates (incl. CI_DEAD_HEAD recovery, which needs the maintainer FORGE_PR_TOKEN).
- Required-check gate.
MERGE_READY only when the branch-protection required set is known and all green; if protection is unreadable, it escalates rather than guessing.
- HEAD-changed abort. Before any mutating action it re-reads the head SHA and aborts if HEAD moved.
Adjacent skills
- Fixing review feedback (CodeRabbit/Greptile/human comments, resolving threads) →
review.
- Opening or pushing the PR →
ship.
- Post-merge health (CI green on main, close issues) →
verify.
- "Where am I / what's in flight" orientation →
status.
Kill-switches
FORGE_SHEPHERD_DISABLE=1 # env: makes automatic firing inert
forge gate disable rail.auto_shepherd # config gate honored by every automatic seam
CI/test (NODE_ENV=test, BUN_ENV=test, CI, GITHUB_ACTIONS, or
GITLAB_CI), dry-run, uninitialized repositories, and disabled paths return
before lease, Kernel-state, or process work. All leave the manual
forge shepherd surface usable; they only stop automatic daemon fire.
State
Public Memory is the durable monitor authority and public Flow reducers restore
its bounded event and watcher-process checkpoints after restart. The per-PR
journal under .forge/pr-monitor/<repo>-<pr>/ remains a compatibility delivery
surface for events --since, not authority. Merged/closed evidence produces one
idempotent terminal MonitorReceipt; conflicting or incomplete replay fails
closed. For 0.1 no receipt grants continuing lease authority: live ownership is
re-probed at consequential boundaries, and canonical LeaseReceipt epoch/scope
plus the same-actor/session ABA fix remain explicitly deferred.
1---2name: shepherd3description: Own open PRs to merge-readiness — autonomously. Forge provides a singleton shepherd daemon — start it with `forge shepherd daemon` — that then watches every open PR, converges CI check state into kernel verdicts, re-runs flaky required checks, reaps orphan watchers, and self-retires when no PRs remain; one-shot passes (forge shepherd <pr>) exist for a single bounded check. Use when a PR was just opened or shipped; when a session starts with open PRs (Forge automatically wakes the singleton; the lease makes a duplicate start a safe no-op); when the user asks "why isn't my PR merging", "what's blocking the PR", "is the PR ready", "did a check fail / go red"; when asked to "keep watching / keep an eye on / babysit my PRs"; or to read PR verdicts (forge shepherd <pr> --pull --json, forge shepherd events). NEVER merges and never resolves review threads — fixing review feedback is `review`; opening the PR is `ship`; post-merge health is `verify`; general orientation is `status`.4---56Forge owns your open PRs. A singleton daemon converges every open PR toward merge-readiness; you read its verdicts and only step in on escalations. Never merges, never resolves review threads.78# Shepherd910`shepherd` is the PR-ownership utility, not a workflow stage. It has two modes over the **single `forge shepherd` verb**:1112- **Daemon (default ownership):** `forge shepherd daemon` is a machine-wide singleton for this repo. It heartbeats and converges the whole PR world every ~60s — self-registering hand-opened PRs, restarting killed watchers, reaping verified orphans, converging CI check state into kernel verdicts, retiring merged/closed PRs — then self-retires (releases the lease, kills its children, exits) once no PRs remain open. You do NOT poll by hand; the daemon does.13- **One-shot convergence:** `forge shepherd <pr>` runs the local review preflight, reads one PR's current-head state, takes at most one idempotent Tier-A action, persists bounded deltas/receipts, and exits. Use it for a point-in-time question or one bounded convergence attempt on a single PR.1415## Which mode? (decision rule)1617- User asks about **one PR, right now** ("is #212 ready?", "why is this blocked?") → **one-shot** (`forge shepherd <pr> --pull`).18- Anything **ongoing** — "keep", "until it merges", "watch", "babysit", multiple PRs, or a session that starts with PRs already open → **ensure the daemon is running**.1920## Automatic attachment2122Forge wakes the singleton after a successful supported session start, every23successful push, and every successful non-dry-run ship. Push does not require a24locally resolved PR number; the daemon enumerates all open and draft PRs itself.25Ordinary commands do not launch it.2627The daemon is a **repo singleton** guarded by an O_EXCL lease, so no liveness28check is needed first. Forge prefers a supplied harness background shell; its29bare-CLI detached fallback launches from the stable common repository root, not30the current disposable worktree. Agents never launch a detached spawn directly.31(A `forge prime` daemon-liveness line remains the separate W-S5 follow-up.)3233## Reading verdicts (the common case)3435```bash36forge shepherd <pr> --pull --json # actionable payload: WHY blocked + exactly what to fix37forge shepherd <pr> --bundle --json # the COMPLETE read-only PR-state bundle38forge shepherd events <pr> --since <seq> # only the new events since sequence <seq>39```4041`--pull` is strictly read-only (dry-run pass: no rerun, no rebase, no merge, no thread resolution). It returns one bounded, actionable-only payload — `blockers[]`, classified `requiredChecks`, failed-check log `failures[]` (matrix-deduped), and every unresolved `reviewThreads[]` — so you get "everything blocking this PR + what to fix" in one call. Passing checks and satisfied policy are omitted.4243A plain `forge shepherd <pr>` also returns a consolidated `localPreflight`, at44most 128 `deltas`, `receiptIds`, and one explicit `handoff`. The preflight probes45CodeRabbit and runs strict lint, Sonar parity, structural drift, and affected46tests. An unavailable or unauthenticated CodeRabbit CLI is reported as47`UNAVAILABLE`/`INCOMPLETE`, never as a fabricated pass; deterministic failures or48actual review findings make the remote decision read-only for that pass. A49checkout that is not the exact PR head is `NOT_APPLICABLE`, never reviewed as if50it were authoritative.5152### Verdict vocabulary (collapsed, W-S1)5354| Verdict | Meaning |55| --- | --- |56| `MERGE_READY` | Required checks green, branch up to date — hand off to a human to merge. |57| `PENDING` | A Tier-A action was taken, or checks are still running — await the next tick/pass. |58| `INCOMPLETE` | Exact-head local review or durable convergence evidence is unavailable — fail closed and retry after evidence is restored. |59| `BLOCKED` | Something actionable blocks merge (failing/missing/skipped required check, conflict, behind, unresolved threads, changes requested). Read `blockers[]`. |60| `CI_DEAD_HEAD` | The head has no required checks running (e.g. an auto-update authored by `GITHUB_TOKEN` never re-triggered CI). Recovery is an **escalation, not an autonomous Tier-A rerun**: it needs a maintainer-provided `FORGE_PR_TOKEN` (contents + pull-requests + checks) to re-author the push so CI re-triggers. |61| `ESCALATE` | A Tier-C condition (conflict, unreadable required set, persistent failure, oscillation, budget exhaustion). Context is posted to the PR. |62| `HARD_STOP` | A permanent auth/scope failure retrying cannot fix — a human must widen token scope. |6364## Trigger scenario → command6566| Situation | Command |67| --- | --- |68| PR just opened / shipped | automatic singleton wake; read with `forge shepherd <pr> --pull` |69| Session starts with open PRs | automatic singleton wake |70| "Why isn't my PR merging / what's blocking it" | `forge shepherd <pr> --pull` |71| "Is the PR ready?" | `forge shepherd <pr> --pull` (read `MERGE_READY`) |72| "A check failed / went red" | `forge shepherd <pr> --pull --json` (read `failures[]`) |73| "Keep watching / babysit my PRs" | ensure `forge shepherd daemon` running |74| Read incremental deltas | `forge shepherd events <pr> --since <seq>` |7576## Boundaries (kept — true of both modes)7778- **Never merges.** No merge action, no server-side auto-merge latch. Terminates at `MERGE_READY` and hands off — a human merges in the GitHub UI.79- **Never resolves review threads.** It refreshes a single **sticky** status comment; thread *resolution* is semantic and stays with `review`.80- **Action ladder.** Tier-A (autonomous, idempotent): re-run a flaky **required** check (rerun-budget capped); refresh the single **sticky** status comment — an *upsert*, never an append, so the ~60s daemon loop cannot post duplicate comments. Tier-B (opt-in, default OFF): `--auto-rebase` rebases onto base and force-pushes with lease — a lease rejection is a hard-stop, never re-armed. Tier-C: everything else escalates (incl. `CI_DEAD_HEAD` recovery, which needs the maintainer `FORGE_PR_TOKEN`).81- **Required-check gate.** `MERGE_READY` only when the branch-protection required set is *known* and all green; if protection is unreadable, it escalates rather than guessing.82- **HEAD-changed abort.** Before any mutating action it re-reads the head SHA and aborts if HEAD moved.8384## Adjacent skills8586- Fixing review feedback (CodeRabbit/Greptile/human comments, resolving threads) → `review`.87- Opening or pushing the PR → `ship`.88- Post-merge health (CI green on main, close issues) → `verify`.89- "Where am I / what's in flight" orientation → `status`.9091## Kill-switches9293```bash94FORGE_SHEPHERD_DISABLE=1 # env: makes automatic firing inert95forge gate disable rail.auto_shepherd # config gate honored by every automatic seam96```9798CI/test (`NODE_ENV=test`, `BUN_ENV=test`, `CI`, `GITHUB_ACTIONS`, or99`GITLAB_CI`), dry-run, uninitialized repositories, and disabled paths return100before lease, Kernel-state, or process work. All leave the manual101`forge shepherd` surface usable; they only stop automatic daemon fire.102103## State104105Public Memory is the durable monitor authority and public Flow reducers restore106its bounded event and watcher-process checkpoints after restart. The per-PR107journal under `.forge/pr-monitor/<repo>-<pr>/` remains a compatibility delivery108surface for `events --since`, not authority. Merged/closed evidence produces one109idempotent terminal MonitorReceipt; conflicting or incomplete replay fails110closed. For 0.1 no receipt grants continuing lease authority: live ownership is111re-probed at consequential boundaries, and canonical LeaseReceipt epoch/scope112plus the same-actor/session ABA fix remain explicitly deferred.