blacksmith-orchestrate
Orchestrate many forge runs at once: analyze which tasks collide on the same files, schedule the colliding ones sequentially, and run the rest in parallel worktrees at the right model tier and review depth. Use when the user runs /blacksmith-orchestrate or asks to ship several is
Install
npx skills add https://github.com/radimsem/forge-skills/tree/main/skills/blacksmith-orchestrate
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install radimsem-forge-skills@llmmart
git clone https://github.com/radimsem/forge-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole radimsem/forge-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Blacksmith Orchestrate
Decide which tasks run, in what order, at what depth, by which model, in which worktree — then dispatch one forge run per task.
Overview
This is a wrapper around forge, not a fork of it. It never reimplements a forge step: every task it dispatches runs the ordinary twelve-step forge workflow, and everything this skill adds is a decision about those runs — which ones may run at the same time, how deep each one goes, and where each one's work lives.
Nine numbered steps, split by one gate, deliberately mirroring forge's shape:
Part 1 — Plan (1–5): normalize → materialize → analyze → schedule → [GATE] battle plan
Part 2 — Execute (6–9): provision → dispatch waves → relay → close-out
gate held: no worktree is created and no forge Part 2 runs until "yes, forge them"
| Step | Action |
|---|---|
| 1 | Parse the invocation; split orchestrator flags from pass-through forge flags; route the work source into a canonical task list |
| 2 | Materialize refs — spec-file and free-form routes get real issue refs via /to-tickets, or synthetic T1..Tn IDs if the user declines filing |
| 3 | Analyze — route each task to the plan-sourced path or the scout fan-out |
| 4 | Build the collision graph, triage matrix and worktree grouping → scheduling waves |
| 5 | [GATE] one battle plan, one approval |
| 6 | Provision worktrees; write the run ledger |
| 7 | Dispatch waves — one agent per task at its assigned model tier and forge depth |
| 8 | Relay — prove a blocker's changes are on the dependent's base, rebase, release |
| 9 | Close-out — aggregate report, PR list, worktree cleanup reminders, self-evolution |
Forge's hard floors are inherited unchanged, and orchestrating many runs never relaxes them: every run's Step 12 /goal verification still has to pass before that run assembles a commit, and no dispatched run ever writes back to the source issue or ticket it was dispatched for — that stays with the user under every flag combination, afk included. That is narrower than "never writes to a tracker": Step 2 files new issues via /to-tickets for the plan and free-form routes, including under automode, exactly as forge's own Step 10 sanctions filing spin-off issues it did not author — see references/entry-routes.md. Whether a dispatched run may also commit and open a PR for its own task is a separate question, resolved three ways: by default, the Step 5 "yes, forge them" approval is the Step 12 commit-and-PR selection forge requires, granted once for every task the approved plan named (see Step 7); under automode without afk, nobody made that selection, so every dispatched run stops at its own Step 12 plan-only output, exactly as a standalone automode forge run does, and nothing is pushed; afk is the single sanctioned exception, authorizing unattended commit-and-PR as well as the blocking-PR merge it already governs — it is labelled that way everywhere it appears, so the exception stays auditable instead of becoming a quiet contradiction. Terminal PRs and worktree cleanup stay with the user under every flag combination, afk included.
"The agent" means whatever agent runs this skill, and "one agent per task" means whatever subagent or parallel-run mechanism the runtime exposes. Adapt every reference — config directory, agent guide, interview UI, host CLI — to your runtime, exactly as forge does. If the runtime cannot run work in parallel at all, say so and fall back to running the waves sequentially rather than pretending to fan out.
Runtime
This skill is Claude-Code-first, degrading elsewhere in the same way forge's codex and coderabbit flags do: each surface below is used where the host provides it, and the workflow still completes, one step slower, where it does not.
| Surface | Used for | Why |
|---|---|---|
| Workflow tool | Step 3b scout fan-out | bounded parallel fan-out, schema-validated returns, keeps N proposals out of the orchestrator's context |
| Agent tool | Step 7 implementation dispatch | per-agent model selection, and the human gates and multi-hour parks live in the main loop |
| Ledger file | Steps 6–9 state | survives /compact, crash and resume |
This is the same runtime-parallelism degradation the Overview already states — "If the runtime cannot run work in parallel at all, say so and fall back to running [...] sequentially rather than pretending to fan out." This section does not restate that rule independently; it is the same rule applied to the three Claude-Code surfaces above. Concretely here: on a non-Claude-Code runtime the skill degrades with a one-line warning to sequential forge runs in dependency order, one worktree per component, no afk, and inline analysis instead of a scout fan-out.
Parameters
Parse the invocation as /blacksmith-orchestrate <work-source> [orchestrator-flags] [forge-flags]. Flag words are orthogonal and can appear anywhere in the request, exactly as in forge. The <work-source> is one of five routes — one or more issue refs, a tracker container such as a milestone or epic or label, plan <path>, a quoted free-form goal, or resume — and routes may combine in a single invocation, so every task carries the route it arrived by. Read references/entry-routes.md for the route table, how the routes normalize into one task list, and how containers expand.
Full flag matrix (effects, composition rules, conflicts): references/flags.md.
Orchestrator flags
These are consumed by the orchestrator and are never passed through to a forge run. Each row's detail file owns that flag's behavior.
| Flag | Effect | Detail |
|---|---|---|
afk |
After a 5-minute quiet timeout, self-verify and merge blocking PRs only — the single sanctioned exception to forge's never-auto-push floor | references/afk.md |
resume |
Resume a run from its ledger instead of starting a new one | references/ledger.md |
budget <n> |
Token ceiling for the run, with a deterministic degradation ladder | references/scheduling.md |
strict |
No depth downgrade; every task runs full forge | references/triage.md |
stack |
Blocked tasks on a soft edge base off the blocker's branch and open stacked PRs; hard-edge blocks still park | references/scheduling.md |
rescout |
Force scout analysis even where dispatch-ready plans exist | references/plan-sourced.md |
max <n> |
Concurrent implementation agents; default 4 |
references/scheduling.md |
dry |
Emit the battle plan and stop; dispatch nothing | references/battle-plan.md |
unified / split |
Override worktree grouping: unified puts a coupled cluster in one worktree behind one PR, split gives every task its own |
references/scheduling.md |
plan <path> |
Source tasks from a written implementation plan; each plan task becomes one orchestration task | references/plan-sourced.md |
Every other flag forge understands passes through unchanged to every dispatched run: automode, docs, tdd, lookup, secure, changelog, ci-watch, compress, implement, codex, codex challenge, codex impl, coderabbit, and code-review. Two of forge's flags are overridden rather than passed through as written. worktree is always implied and orchestrator-managed, because Step 6 provisions, names and tracks every worktree itself and a run-level worktree decision cannot be delegated to the individual runs; passing it explicitly is accepted with a one-line note rather than treated as an error, since the user is asking for what already happens. automode lifts the Step 5 battle-plan gate exactly as it lifts forge's Step 6 gate, and still passes through to each dispatched run; it does not lift any hard floor named in the Overview.
When to Use
/blacksmith-orchestrate <refs>with two or more issue or ticket refs.- "ship this milestone", "implement this plan", "fix these five issues".
- A written implementation plan whose tasks should all land in one sitting.
- Any request to run several forge-shaped pieces of work together, where the ordering between them matters.
- Work too large even for one battle plan — more than one agent session can hold — goes to
/wayfinderfirst: it maps the destination as decision tickets on the tracker, and the resulting ticket set comes back into this skill through the ordinary refs or container route, blocking edges included.
Don't use when:
- A single issue, ticket or PR is in scope. Use
/forge <ref>: with one task there is no collision graph, no wave to schedule and no topology to choose, so the wrapper adds a second gate and buys nothing. - No work source is given. Ask which issues, milestone or plan is meant rather than inferring a task list from the repo, for the reason in Step 1.
Step 1 — Parse the invocation and normalize the work source
Split the invocation into three parts: the work source, the orchestrator flags from the Parameters table, and every remaining flag, which is forge pass-through. A token that matches neither the orchestrator flag table nor forge's own flag list is not assumed to be a pass-through flag: stop, name the token back to the user, and suggest the near miss when there is an obvious one — strct almost certainly meant strict. Forge lives with the same open-world assumption and pays for it once; here an unrecognized token is forwarded to every dispatched run and silently dropped by each, so a user who typed strct gets depth triage applied across the whole batch and finds out at close-out, which is the one place the cost of a typo scales with the wave. Route the work source into one canonical task list in which each task records the route it came from, because Step 3 chooses its analysis path per task rather than per run; the five routes, their combination rules and deduplication are in references/entry-routes.md. If no work source resolves — a bare invocation, or flags with nothing to act on — stop and ask which issues, milestone or plan is meant; inferring a batch from the repo would dispatch work nobody asked for across several worktrees at once, which is far more expensive to undo than one question is to ask. If both unified and split are present, stop and report the conflict instead of applying a precedence, because the two ask for opposite PR topologies and silently honouring one would hide from the user which shape actually shipped.
Step 2 — Materialize refs
Every task needs a stable identifier before analysis, because the battle plan, the ledger, the worktree names and each dispatched forge run all address tasks by ref. Ref and container routes already have one; the plan and free-form routes do not, so file real issues for those tasks with /to-tickets and adopt the returned refs, or fall back to synthetic T1..Tn IDs when the user declines filing — a synthetic ID is local to this run and never reaches a tracker, so any later step that would write to a tracker for such a task skips that write and names the skip in the Step 9 report. The free-form route carries one exception: under automode its tasks are never filed, unless the user named the split themselves — for example by enumerating the tasks in the goal, or by re-invoking with refs once they have seen the recorded split — because otherwise the split is the agent's own assumption rather than a detail of a human-written request; see the free-form decomposition section of references/entry-routes.md. If a ref does not resolve to something its tracker can return, name it and ask whether to continue without it; under automode, drop it and record the omission in Step 9, because a silently missing task is indistinguishable from one that was never requested, and that is the single failure a batch run must never hide. Route mechanics — the /to-tickets handoff, container expansion and which trackers are supported — are in references/entry-routes.md.
Scouts never interview
This rule is stated here because it governs every ref Step 2 hands onward. A scout dispatched in Step 3 that hits a forge Step 4 context gap returns the open question rather than asking it, and never blocks waiting for an answer. The orchestrator batches every task's open questions into one consolidated interview attached to the Step 5 gate, using forge's proposed-answer format with the most likely option marked (Recommended) and "Other" implicit. A scout that interviewed on its own would stall a parallel fan-out behind N separate prompts and would split into N approvals the single approval the gate exists to collect. Under automode the (Recommended) answer is taken for every open question and the assumption is recorded in the battle plan, exactly as forge does at its own Step 4.
Step 3 — Analyze
Every task needs to know which files it will touch before Step 4 can build a collision graph, and there are two ways to learn that: read it off a plan someone already wrote, or pay an agent to go find out. Route per task, not per run — a plan covering three of five tasks is dispatch-ready for those three and scouts only the other two, because the two routes answer the same question at very different cost and a task's route says nothing about its neighbor's.
| Route | When | Cost |
|---|---|---|
| 3a — plan-sourced | The task's route is plan and its slice passes the dispatch-ready check |
Zero agent spawns; dispatches straight through /forge plan <path> |
| 3b — scout fan-out | Every other task, plus any plan-sourced task that fails dispatch-ready or is stale |
One forge Part 1 run per task |
Step 3a — Plan-sourced
A task routed to plan in Step 1 is analyzed by reading its own plan slice rather than by dispatching an agent to rediscover what the plan author already wrote down. The dispatch-ready check, the freshness guard that can void it, and the fallback to 3b when either fails are all in references/plan-sourced.md — read it before implementing this step; its rules are not restated here.
Step 3b — Scout fan-out
Every task that is not dispatch-ready — whether it never had a plan slice or fell out of one — is analyzed by dispatching one forge Part 1 run (Steps 1 through 6, stopping at the gate) per task, run in parallel. The fan-out runs as one Workflow invocation using scripts/scout-fanout.mjs, which returns one structured proposal per task rather than prose the orchestrator would otherwise have to parse. Schema validation on that return shape is enforced at the tool layer, so a malformed scout return is retried by the runtime itself rather than parsed defensively here.
Per the Overview's runtime-parallelism rule, applied here to Step 3b specifically: on a runtime without a Workflow surface, the analysis runs inline and sequentially instead, with a one-line warning naming why.
Step 4 — Schedule
Step 3's filesToTouch, symbols, declaredBlockers, blastRadius and difficulty feed Step 4. The first four build the collision graph in references/collision-graph.md: two tasks collide when their file sets intersect, one declares the other a blocker, or a plan orders one before the other, and every edge is oriented by a deterministic priority — overlap-derived edges are acyclic by construction, stated (declared or plan-order) edges are checked for cycles and dropped back to priority order when one turns up. Every task is then triaged along two independent axes — difficulty picks the model tier, blast radius picks the forge depth, both documented in references/triage.md — before the graph's connected components are grouped into worktrees, per references/scheduling.md: a lone task gets its own worktree, a small tightly-coupled component may run unified in one worktree behind one PR, and everything larger splits one worktree per task. Scheduling then emits waves from the oriented graph — every task with no unsatisfied blocker joins wave 1 up to the max concurrency ceiling, and a blocked task enters a later wave only once its blocker has relayed — and none of it dispatches anything; the whole plan surfaces at the Step 5 gate.
Step 5 — The gate
Everything Steps 1 through 4 produced is assembled into one battle plan and shown to the user. Do not create any worktree and do not dispatch any forge run until the user approves. This is the same invariant forge states at its own Step 6, one level up: nothing here touches the filesystem, opens a branch, or spends an agent's budget on implementation until approval is explicit. Read references/battle-plan.md for the literal artifact format, its required elements, and the exact approval semantics — they are not restated here.
automode is the only sanctioned bypass, and it lifts this gate exactly as it lifts forge's Step 6: the plan is still assembled and still shown, but as a record the run already acted on rather than a question awaiting an answer. Lifting this gate does not lift any hard floor named in the Overview — no dispatched run auto-commits, auto-pushes, or writes back to a tracker under automode either.
Step 6 — Provision
Create one worktree per group, per the topology Step 4 decided — a lone task gets its own worktree, a unified component shares one worktree and one branch, a split component gets one worktree per task — composing superpowers:using-git-worktrees at every worktree exactly as forge's worktree flag composes it at its own Step 3, so naming, layout and cleanup match what a single forge run already does rather than inventing a second worktree convention. Write the run ledger before dispatching anything: an interrupted run is resumable only because the ledger already recorded every worktree and every task's planned state before Step 7 touched any of them. Read references/ledger.md for the schema, the task-state vocabulary, and the resume contract.
Step 7 — Dispatch
Dispatch one agent per task, at the tier and depth Step 4 assigned, running in that task's worktree. A plan-sourced task is dispatched as /forge plan <slice> <pass-through-flags>; a scouted task is dispatched as /forge <ref> <pass-through-flags> with the scout's proposal supplied as the already-approved plan, so the dispatched run does not re-derive what Step 3b already found. Concurrency is capped at max (default 4), against the waves Step 4 already computed. Update the ledger row on every state change, so references/ledger.md's task-state vocabulary is never stale by more than one transition. On a runtime without a parallel agent surface, dispatch sequentially in dependency order instead, with a one-line warning naming why.
The same approval that supplied each run's Step 5 plan above also supplies its Step 12 commit-and-PR selection, for exactly the tasks named on the approved battle plan and no others — forge's Step 12 requires an explicit human selection before any run commits or opens a PR, and "yes, forge them" against a plan that named every task is that selection, made once for all of them. Under automode without afk, no human made it, so every dispatched run's own Step 12 stops at its plan-only output instead, exactly as a standalone automode forge run does; afk authorizes the commit-and-PR selection unattended, the same way it already authorizes the blocking-PR merge at Step 8. See references/afk.md for the full three-way rule and references/battle-plan.md for what the plan must show before it can grant this authorization.
Step 8 — Relay
When a blocked task's blocker lands, the dependent is not released on a merged label alone — the orchestrator proves the blocker's changes are actually reachable from the dependent's base with an ancestor check, falling back to a content-and-PR-number proof when a squash or rebase merge has rewritten the SHA, then rebases the dependent onto the current base before it dispatches. The full proof sequence, the fallback, and what happens without a host PR CLI at all are in references/relay.md.
By default, merge authority stays with the user: the orchestrator notifies that a blocking PR is ready and parks — it never merges, on this run or any other, automode included. references/afk.md documents the one flag that changes that, afk, the single sanctioned exception to forge's no-auto-push floor, and the seven-item checklist every one of its blocking-PR merges has to pass in full before it acts.
Step 9 — Close-out
The orchestrator emits one aggregate report: per task, its PR, branch, or plan-only file — whichever its own Step 12 actually produced, per the three-way commit-and-PR rule in the Overview and Step 7 — final state, depth, tier and /goal result; every task budget deferred or downgraded, named explicitly by ref and by which rung of the degradation ladder acted on it; every parked task and precisely what it is waiting on, not just that it is waiting — including, under automode without afk, any task left parked for the rest of the run because its blocker never produced anything the relay could prove ancestry against, per references/relay.md's "Without a landable commit" section; and a worktree cleanup reminder for every worktree Step 6 provisioned. Cleanup is never auto-run, inheriting forge's own worktree rule that losing in-progress state on inferred completion is the wrong default — a task reading merged in the ledger is still the orchestrator's inference from the evidence it gathered, not a claim the user has personally confirmed, and removing a worktree on that inference risks deleting something the user still wanted to look at.
Two forge steps are hoisted to the orchestrator, because running them per task would produce N conflicting writes to the same targets:
| Forge step | Dispatched run does | Orchestrator does |
|---|---|---|
Step 10 — spin-off issues via /to-tickets |
collects candidates and reports them; files nothing | dedupes across all tasks, then files once under forge's normal rules |
| Step 11 — self-evolution | reports candidate lessons; writes nothing | dedupes, and proposes a single skill, rule, guide or memory edit |
A worktree is exactly the boundary that keeps N parallel forge runs from touching each other's state, and both hoisted steps write outside it. Running either step inside every dispatched run would turn that shared, out-of-worktree target into an N-way race: N runs each drafting and filing their own spin-off issues for overlapping observations duplicates tracker noise nobody asked for, and N runs each proposing their own skill or memory edit competes to write the same config file from N worktrees that cannot see one another's in-flight change. The orchestrator, which alone can see every task's candidates at once, is the one place the write is safe to make.
Orchestrator-level self-evolution has its own subject matter, distinct from what any single dispatched run could observe: triage misses that required a runtime promotion, edges the collision graph over- or under-serialized, and plan slices that proved stale. Those lessons belong in references/anti-patterns.md, this skill's own canonical home for them, exactly as forge's is for forge's.
The ledger is written to its final state before the report is emitted, so a run that is reported is always a run that can be resumed or audited — the report is read off the same file resume would read, never off a separate in-memory summary that could drift from it.
Anti-patterns & Red Flags
See references/anti-patterns.md for the red-flag table — the canonical home for lessons learned during an orchestration run.
Files (forge-skills)
-
references
-
afk.md 5.9 KB
# Blacksmith — `afk`: the sanctioned unattended-action exception The one flag in this skill that lets the orchestrator act without a human present: merge a blocking PR without a human clicking merge, and — combined with `automode` — commit and open a PR without a human having approved the battle plan. Loaded by Step 7 and Step 12 whenever `automode afk` grants a dispatched run its commit-and-PR selection, and by Step 8 whenever a blocking PR is ready and `afk` is set. ## Manual verification recipe A blocking PR with a red CI check, run under `afk`. Expected: no merge — check 2 of the verify checklist below fails immediately — a notification that the PR is red and parked, and the task stays parked. The next poll re-evaluates the same checklist from scratch; there is no retry loop waiting on this one check to turn green. ## Default — merge authority is human Without `afk`, the orchestrator notifies the user that a blocking PR is ready, and parks. It never merges. This is the default for every run, `automode` included, and it preserves forge's stated hard floor — never auto-commit, auto-push, or write back to a tracker, even under `automode` — unchanged. Orchestrating many runs at once does not relax that floor for any single one of them. ## With `afk` `afk` replaces the indefinite park with a bounded wait: notify → a 5-minute quiet timeout (no user message in the session, and the notification itself unacknowledged) → the orchestrator self-verifies the PR against the checklist below. The timeout exists so a user actively driving the session is never overridden mid-thought by an automatic merge they had no chance to weigh in on; silence for the full window is what stands in for that chance having passed. ## The verify checklist Merging requires **every** item to pass, verbatim from spec §8: 1. the PR is mergeable with no conflicts; 2. CI is green for the PR head SHA across all required checks; 3. that task's forge review loop converged to zero actionable findings, read from the ledger — at `patch` depth, where [triage.md](triage.md) runs no review loop at all, this item is satisfied instead by the orchestrator's own diff read that depth already requires, per [triage.md](triage.md)'s depth table, and is not treated as vacuously passed just because there was no loop to converge; 4. there are zero unresolved human review comments or change requests; 5. the diff's file list is a subset of the approved proposal's file list; 6. `/goal` verifies green in the worktree at the PR head; 7. the base branch permits the merge. Any single failure keeps the PR parked and notifies; there is no merge retry loop — the checks are simply re-evaluated on the next poll, exactly as they would be for a PR with no `afk` verification pending at all. ## Commit-and-PR authorization `afk` is one flag with one meaning — "I am not here, act on my behalf with the stated verification" — and that meaning covers two actions, not one. Besides the blocking-PR merge above, `afk` combined with `automode` is also what authorizes each dispatched run's own Step 12 commit-and-PR selection when no human ever approved the battle plan to begin with: `automode` alone only lifts the Step 5 gate, which is a different thing from granting the Step 12 selection that gate would otherwise have granted on approval, so without `afk` an `automode` run's dispatched tasks stop at Step 12 plan-only, exactly as a standalone `automode` forge run does — nobody delegated the commit. With `afk` added, that delegation is explicit, and each dispatched run commits and opens its PR for the tasks the plan named, subject to the same Step 12 `/goal` verification forge already requires at every depth. This is not a second exception to forge's no-auto-push floor alongside the merge above — it is the same one, covering the same floor, stated here so both of `afk`'s actions are labelled as auditably as each other. Outside `automode`, this half of `afk` is inert: the Step 5 approval already grants the Step 12 selection on its own (see `SKILL.md`'s Overview and Step 7), so there is nothing left for `afk` to additionally authorize there — its only live effect on a non-`automode` run is the blocking-PR merge above. ## Scope limits `afk` merges **only PRs that block another task.** A terminal PR — one nothing in this run waits on — is always left for the user, even under `afk automode`; there is no wave gated on a terminal PR's merge, so nothing about the run's own progress requires it to land on any particular schedule, and taking merge authority the user never delegated for work that was not slowing anything down would spend the exception on a case it was never written to cover. On a host with no PR CLI at all — `gh` or `glab` absent or unauthenticated — there is no PR for `afk` to act on in the first place: components produce local branches instead of PRs, per [relay.md](relay.md)'s "Without a host CLI" section, and `afk` is inert there and says so. This is not a degraded form of the exception; it is the exception having nothing to attach to, the same way a checklist item cannot fail against a PR that does not exist. ## Why this exception exists `afk` is the **single sanctioned exception** to forge's no-auto-push floor, stated here, in `SKILL.md`, and in [flags.md](flags.md) wherever the floor itself is stated, so the exception cannot be read in one place and missed in another. It is opt-in and never a default — the flag has to be named on the invocation, and its absence leaves every run at the Default above, human merge authority, unconditionally. It is written down with its reasoning, rather than left as an emergent behavior of some other flag, precisely so the exception is auditable: a reviewer of this skill's prose can find every place forge's hard floor is touched by reading `flags.md`'s composition rules and this file, instead of discovering a silent contradiction of the README by watching a run merge something nobody approved. -
anti-patterns.md 1.7 KB
# Blacksmith — Anti-patterns This file is the canonical home for lessons learned during an orchestration run, exactly as `references/anti-patterns.md` is forge's — a red flag belongs here the moment it generalizes beyond the run that surfaced it, not scattered into `SKILL.md`. | Red flag | Why it is wrong | |---|---| | Dispatching before battle-plan approval | The gate is the wrapper's contract, exactly as Step 6 is forge's. | | Treating any file overlap as hard | Over-serializes; the two grades exist precisely to avoid this. | | Merging because the PR page says green | The ancestor proof, not the PR UI, releases a relay. | | Downgrading depth on high blast radius | Floor 2. The auth-one-liner case is the reason the axes are separate. | | Letting the implementing model be the only reviewer | Floor 4, generalized from `codex impl`. | | Dispatching a plan-sourced task without the freshness check | Nothing else re-validated that plan against the repo. | | Treating disjoint file sets as proof of independence | Semantic conflicts exist; the rebase-then-`/goal` rule is the defense. | | Auto-removing worktrees | Inherited from forge's `worktree` flag: losing in-progress state on inferred completion is the wrong default. | | Silent truncation under `budget` | Anything deferred or downgraded must be named in the close-out. | | A check written without its failure branch | An agent hitting the failed check has no defined behavior, so two runs diverge; every check needs its branch and the reason. | | A classification rule that routes missing data to the permissive branch | An empty list satisfies a "disjoint"-style predicate vacuously, so unknown scope silently takes the parallel-safe path; missing data must route to the conservative branch. | -
battle-plan.md 6.2 KB
# Blacksmith — the battle plan and the gate The literal format of the one artifact Step 5 emits, and the approval semantics that govern it. Loaded by Step 5. This is the orchestrator's analogue of forge's `proposal-template.md`: the format below is a contract, not an example — an agent assembling the battle plan follows it verbatim. ## Manual verification recipe Run any invocation with two or more tasks and `dry` set. Expected: the battle plan is emitted in full, in the format below, and the run stops there — no worktree is created, no ledger is written and no agent is dispatched, regardless of any other flag present in the same invocation (`afk` and `budget` are both reported as inert on the plan itself, per [flags.md](flags.md)'s composition rules, rather than silently having no visible effect). ## Format The gate emits exactly one artifact and waits for one approval. ``` BATTLE PLAN — 4 tasks, 2 waves, split topology wave 1 (parallel, 3 worktrees) #42 parser rewrite Opus × full wt-42 scout #51 docs typo Haiku × patch wt-51 plan #task-1 #60 CLI flag Sonnet × lite wt-60 scout wave 2 (blocked) #43 parser BOM fix Sonnet × full wt-43 plan #task-2 blocked on #42 — hard overlap, src/parser.ts:parseHeader relay: #43 releases when #42's changes are proven on its base merge: human (no `afk`) — you are notified when #101 is ready commit: approving this plan authorizes Step 12 commit + PR for #42, #43, #51, #60 budget: no ceiling set stale: none open questions (2) — answer before approving #60 Which module owns the new flag? (Recommended: src/cli/config.ts) #42 Keep legacy BOM behaviour behind a flag? (Recommended: no) [ yes, forge them ] [ amend … ] [ drop a task ] ``` ## Free-form task split When any task in the run entered through the free-form route, the battle plan renders the split before the wave listing: one line per proposed task, each showing the fragment of the original goal it was derived from, so the user can see and correct a decomposition they never wrote — per [entry-routes.md](entry-routes.md#free-form-decomposition), this rendering is not optional for that route and does not depend on how confident the split felt. ``` free-form split — "add auth, fix the parser, bump deps" T1 add auth ← "add auth" T2 fix the parser ← "fix the parser" T3 bump deps ← "bump deps" ``` Under `automode`, the gate itself is skipped (see Approval semantics below), but the split is still recorded — as a **named assumption**, not as an ordinary battle-plan line, because under `automode` nobody reviews the split before it drives dispatch: ``` assumptions free-form split assumed (automode, unreviewed) — 3 tasks from 1 goal: T1 add auth ← "add auth" T2 fix the parser ← "fix the parser" T3 bump deps ← "bump deps" ``` ## Required elements Every task's ref, title, tier, depth, worktree and analysis route; every edge with its overlap grade and the exact file or symbol that produced it; the topology decision; any plan slice flagged stale; the merge-authority mode in force; the commit-and-PR authorization this approval will grant — every task it covers, named explicitly, and none it does not (see Commit-and-PR authorization below); anything `budget` will defer; and the consolidated interview. A batch run accumulates undetermined and out-of-run conditions from every reference this step composes — `symbols undeterminable` and `declared blocker outside this run: <ref>` from [collision-graph.md](collision-graph.md), `difficulty undeterminable: <ref>` and `blastRadius undeterminable: <ref>` from [triage.md](triage.md), `freshness unverified: plan not yet committed` from `plan-sourced.md`, and `unified (mixed kind)` from [scheduling.md](scheduling.md). These are one class, not a list to duplicate here: any condition a task or edge carries when it reaches the gate is rendered on the line for the task or edge it describes, in the wording its owning reference already defines, never re-derived or re-worded at this step. Battle-plan assembly does not invent a parallel vocabulary for a condition an earlier step already named — it renders what it is handed. ## Commit-and-PR authorization Approving the plan is also the Step 12 selection forge requires before any dispatched run may commit or open a PR: "yes, forge them" said against a plan that names every task is that selection, made once for all of them rather than once per run — the same relay Step 7 already performs when it supplies the scout's proposal as an already-approved Step 5 plan. The plan states this on its face, in the `commit:` line above, naming exactly the tasks the approval covers, so the human sees what they are authorizing before they say yes; an amendment that adds or drops a task changes what that line covers and re-asks for approval like any other amendment. Under `automode`, no human says "yes, forge them" — the gate is skipped, not answered — so this authorization is never granted by default, and every dispatched run's own Step 12 stops at its plan-only output instead: see `automode`'s row in [flags.md](flags.md) and [afk.md](afk.md) for the one flag, `afk`, that grants it anyway. ## Approval semantics **"yes, forge them"** is the only phrase that provisions worktrees. Nothing short of it — not silence, not a question answered, not an amendment accepted — starts Step 6. An amendment — re-tier, re-depth, re-group, drop a task, or force an edge the graph missed — revises the plan and re-asks; the revised plan is a new artifact requiring its own approval, not an implicit continuation of the one before it. Under `automode` the gate is skipped, `(Recommended)` answers are taken for every open question, and the battle plan is emitted as a record rather than a question — the same plan, the same required elements, but printed after the fact rather than awaited. `dry` emits the plan and stops regardless of any other flag: it outranks `automode` for the purpose of stopping the run (there is still no gate to skip, because there is nothing past it to run), and it makes `afk` and `budget` inert per [flags.md](flags.md)'s composition rules. -
collision-graph.md 12.4 KB
# Blacksmith — the collision graph How Step 4 turns each task's scout fields — `filesToTouch`, `symbols`, `declaredBlockers`, `blastRadius` — into a directed graph of who must wait for whom, before triage or scheduling sees a single task. Loaded by Step 4. ## Manual verification recipe **Case 1 — hard overlap.** Two tasks in the run whose scout returns both name `src/parser.ts` in `filesToTouch` and both name `parseHeader` in `symbols`. Expected: one edge between the two tasks, graded **hard**. Orientation picks the blocker by the four-step priority below, and the dependent parks — it is not dispatched into a wave until the blocker's PR has landed and relayed. **Case 2 — soft overlap.** The same two tasks, but with disjoint `symbols` — one names only `parseHeader`, the other only `stripBom`, both still against `src/parser.ts`. Expected: the same edge, graded **soft** instead of hard. The default schedule still serializes it — soft is a conservative default, not a free pass to parallelize — so the dependent still waits for its wave. **Case 3 — the same soft pair, re-run with `stack`.** Expected: both tasks dispatch in wave 1. The dependent's worktree bases off the blocker's branch rather than off the run's base, and its PR opens stacked on the blocker's, per `stack`'s effect in [flags.md](flags.md). **Case 4 — `declaredBlockers` edge, no file overlap.** Two tasks whose `filesToTouch` sets are entirely disjoint, but one names the other in `declaredBlockers`. Expected: an edge is drawn anyway, graded **hard**, oriented in the declared direction — the dependent parks even though nothing about their file lists would have connected them on file overlap alone. **Case 5 — plan-order edge, no file overlap.** Two plan-sourced tasks from the same plan, sequential in the plan's task list, whose `filesToTouch` sets are disjoint, and the plan neither marks them independent nor shows the disjoint-and-no-shared-interface exception from `plan-sourced.md`. Expected: an edge is drawn from plan task order alone, graded **hard**, dependent parks — the same outcome as Case 4, sourced from plan order instead of `declaredBlockers`, since neither task's scout ever ran to populate `declaredBlockers` in the first place. **Case 6 — undeterminable symbols.** Two tasks against the same file, but one task's scout returns `symbols: []` — a whole-file rewrite. Expected: hard-vs-soft cannot be decided from symbols, so the edge defaults to **hard**, with `symbols undeterminable` surfaced on the battle plan — not soft, and not parallel under `stack`, unless the user explicitly overrides the grade at the gate. ## Edges Two tasks draw an edge in any of three ways: their `filesToTouch` sets intersect; one task names the other in `declaredBlockers`; or, for plan-sourced tasks, the plan's task order implies one comes before the other. The first is inferred evidence of a dependency; the other two are stated evidence — an issue author naming a blocker and a plan author sequencing tasks are both stating a dependency the same way — and stated beats inferred, so a `declaredBlockers` or plan-order edge is drawn even when the two tasks' files never touch. Call the latter two **stated edges**, as distinct from the **overlap edges** file intersection draws; both kinds of stated edge are graded **hard** regardless of file overlap, per Overlap grades below — there is no discounted "soft stated" edge, because a stated dependency does not earn a lighter grade for happening to also avoid a shared file. Tickets that were filed via `/to-tickets` (Step 2, or a `/wayfinder`-produced set fed in as refs) already declare their blocking tickets natively — as blocking links on a real tracker, or edge text in the local ticket files — and those declarations read directly into `declaredBlockers`, so a to-tickets-sourced run typically arrives with its stated edges complete rather than relying on scouts to rediscover them. Plan-order edges are subject to the same lifting conditions `plan-sourced.md` already states, not a separate rule here: see [plan-sourced.md#plan-task-order-is-a-declared-dependency-edge](plan-sourced.md#plan-task-order-is-a-declared-dependency-edge) for the two conditions — explicit independence marking, or disjoint file sets **and** no shared interface in the plan's File Structure section — that lift the default blocker-chain assumption between consecutive plan tasks. That rule is not restated here so the two files cannot drift apart. This mechanism matters most exactly where it is easy to miss: a plan-sourced task is never scouted, so it may never populate `declaredBlockers` at all, and without a plan-order edge a dependency `plan-sourced.md` insists must survive would have no other way to reach this graph. One shared path, or one stated-edge mention, is enough to draw an edge; a task's `filesToTouch` set never needs to match another's exactly to collide by file overlap, and two tasks whose file sets are merely overlapping and two tasks whose file sets are identical both draw exactly the one edge that intersection draws, never several. This graph has no notion of "how much" two tasks overlap on files, only whether they do, so a one-file overlap and a six-file overlap on the same pair both produce one edge, distinguished only by the overlap grade below — identical file sets are not automatically graded hard on that basis alone; absent a stated edge, that grade still depends on symbol overlap, per Overlap grades. A task whose `filesToTouch` set is empty and which carries no stated-edge relationship to any other task in the run cannot form an edge with anything, because there is nothing to intersect and nothing stated. This is not a state Step 4 is meant to see: Step 3's dispatch-ready check (3a, condition 1) and the scout schema's field validation (3b) both require a concrete, non-empty file list before a task reaches Step 4. If an empty set arrives anyway and the task is not otherwise connected by a stated edge, Step 4 does not silently schedule that task into its own isolated component as though its scope were confirmed empty — it stops and names the task by ref, because dispatching a task nothing was ever checked to collide with is dispatching against an unverified scope, exactly the situation the dispatch-ready check exists to prevent. **A `declaredBlockers` entry — or a plan's task order — naming a predecessor outside this run's task set cannot draw an edge** — there is no node for that ref in this graph, because it was never scheduled into this run. Step 4 does not block on it: a task cannot be made to wait forever on work that was never dispatched, so the run proceeds without that dependency rather than stalling on one indefinitely. It surfaces prominently on the battle plan as an unmet stated dependency (`declared blocker outside this run: <ref>`, or `plan predecessor outside this run: <ref>`) naming the task and the missing ref, precisely because dropping it silently would hide a real dependency someone wrote down or sequenced. If the user wants that ordering honored, the fix is to add the missing ref to the run — the orchestrator does not guess at scheduling work nobody asked it to run. ## Orientation Edges are always oriented, never left undirected, by a single deterministic priority, verbatim from spec §4: 1. an explicitly declared blocker (`declaredBlockers`, or plan task order) wins; 2. else larger blast radius goes first; 3. else more files touched goes first; 4. else lower ref number. Because orientation follows one total order, **overlap-derived edges are acyclic by construction** — there is no cycle-breaking case and no deadlock detection for that part of the graph. Stated edges — `declaredBlockers` and plan task order alike — are the exception, and the reason is the same property that makes them valuable: their direction is *stated*, not computed from the total order, so two or more of them can disagree with each other. The two-node case is a task naming, or a plan sequencing, a blocker that a lower tier would have ordered the other way — that is tier 1 working as designed, not a conflict, because tier 1 exists precisely to let stated intent outrank the heuristic proxies below it. But a **cycle among stated edges** is a different situation: not one declaration outranking a heuristic, but declarations that outrank each other. The simplest case is two tasks each naming the other in `declaredBlockers`; the general case is any chain that closes on itself through stated edges, mixing `declaredBlockers` and plan-order legs freely — A blocks B by declaration, B blocks C by priority, C blocks A by plan order is still a cycle, because the two stated legs alone contradict the order the third leg would otherwise impose. Step 4 checks the stated edges for a cycle. When it finds one, it drops every edge in that cycle back to priority order (tiers 2 through 4) for orientation, and flags the cycle on the battle plan, naming the tasks and the contradicting declarations. This resolves the cycle without picking a side quietly: a cycle among stated edges means the source issues or the plan contradict themselves about which comes first, and that is information the user needs to see, not something the orchestrator should resolve on its own authority. The mutual two-task case reads as the two-node instance of this same check, not a special case with its own logic. The exception stays bounded. Only stated edges participate in the cycle check; every overlap-derived edge, and every stated edge not part of a cycle, is still oriented by the same total order as before. Once a cycle's edges are dropped back to priority order, tiers 2 through 4 close the graph again exactly as they do everywhere else — tier 4 alone is a strict order over distinct ref numbers, so it always terminates. The graph as a whole is therefore acyclic once the stated-edge cycle check has run, even though that guarantee no longer follows from the total order alone. ## Overlap grades File-level matching over-serializes, so overlap has two grades: | Grade | Condition | Default | |---|---|---| | Hard | same file **and** overlapping symbols or regions | serialize — dependent parks | | Soft | same file, disjoint symbols | serialize (conservative default); runs parallel under `stack` | Grade is decided per file pair by symbol overlap, never by how many files two tasks share: two tasks sharing one file with overlapping symbols on it are graded exactly as hard as two tasks sharing every file with overlapping symbols on at least one of them, because a single genuine collision is enough to require serialization. **When either side's `symbols` list is empty, or the overlap is otherwise undeterminable — a whole-file rewrite is the obvious case — hard-vs-soft cannot be decided from symbols at all, and the grade defaults to hard, not soft.** The reason (`symbols undeterminable`) is surfaced on the battle plan so the user can override it. This is not an arbitrary caution: a soft grade authorizes two agents to edit the same file concurrently once `stack` is set, and granting that on missing information would trade a correctness risk for a throughput gain on the worst possible basis for that trade — the absence of the very data the grade is supposed to be decided from. The user can still force parallelism explicitly at the gate; the point is that going soft on missing data has to be a decision, not a fallthrough. Stated edges (`declaredBlockers` and plan task order) are graded hard unconditionally, by the same default regardless of whether the files overlap at all, per Edges above — stated evidence does not need a shared file, or even known symbols, to earn a hard grade. The battle plan always states which grade produced each edge, so an over-serialization is visible and correctable at the gate. ## Semantic conflicts Disjoint file sets do not prove independence: task A can change an interface that task B's untouched file calls, and neither file-set intersection, `declaredBlockers`, nor plan task order catches it unless someone thought to write or sequence the dependency down — a scout or plan author has to have known enough to name it. No file-level or stated-edge signal can force that knowledge into existence when nobody supplied it. The defense against the conflicts no signal in this graph can see is behavioral rather than static: **every worktree rebases onto the current base and re-runs its `/goal` before its PR is treated as ready.** A PR is verified against the base it will land on, never the base it forked from. -
entry-routes.md 10.6 KB
# Blacksmith — work sources and entry routes How `/blacksmith-orchestrate <work-source>` becomes one canonical task list. Loaded by Steps 1 and 2. ## Manual verification recipe ``` /blacksmith-orchestrate 42 43 plan docs/superpowers/plans/2026-08-21-auth.md dry ``` Expected: Step 1 emits a single task list carrying three or more tasks — `#42` and `#43` tagged with the `refs` route, plus one task per task heading in the plan file tagged with the `plan` route — and no task loses its route tag on the way to Step 3. Because `dry` is set, Step 5 prints the battle plan and the run stops there: no worktree is provisioned and no forge run is dispatched. ## Work sources Five routes converge on one canonical task list. This mirrors forge's Step 1, whose target grammar routes six ref and entry-verb shapes into one workflow. | Form | Route | Example | |---|---|---| | One or more refs | each ref becomes one task, using forge's existing ref grammar verbatim | `/blacksmith-orchestrate 42 43 PROJ-7` | | `milestone <x>` / `epic <KEY>` / `label <name>` | expand the container into an issue list via the tracker, then treat as refs | `/blacksmith-orchestrate milestone 3` | | `plan <path\|glob>` | plan-sourced route (`references/plan-sourced.md`) | `/blacksmith-orchestrate plan docs/superpowers/plans/2026-08-21-auth.md` | | A quoted free-form goal | orchestrator splits the prose into distinct tasks, always surfaces that split at the gate, then materializes refs (Step 2) | `/blacksmith-orchestrate "add auth, fix the parser, bump deps"` | | `resume [run-id]` | resume from a ledger (`references/ledger.md`) | `/blacksmith-orchestrate resume` | Ref shapes are forge's, not new ones: a bare number, `#N` or `issue N` is a git-host issue, a key-shaped token is a Jira or Linear issue, and the `ticket` and `linear` keywords force their tracker. Read forge's own target grammar in [../../forge/SKILL.md](../../forge/SKILL.md) rather than re-deriving it here — a second, drifting copy of the ref grammar is exactly the duplication this skill exists to avoid. ## Normalization Routes may combine. Refs and a `plan` may appear in one invocation, and a container may be mixed with loose refs. **Every task carries its own route**, recorded on the task and preserved through Steps 2 through 5, because Step 3 decides between the plan-sourced path and a scout fan-out **per task, not per run** — a plan covering three of five tasks scouts only the other two, and that decision is impossible if the route was collapsed into a single run-level mode. Deduplicate by resolved ref after every route has been expanded, not before: a milestone and a loose ref frequently name the same issue, and running that issue twice would put two agents in two worktrees on the same files with no collision edge between them, since the collision graph reasons about distinct tasks. Two tasks from *different* routes that cannot be proven to be the same ref are treated as two tasks, even when they look like the same work — for example a plan slice and an issue that describe the same fix. Do not silently merge them. Name both at the Step 5 gate and let the user drop one, because merging on a guess would drop work the user asked for, while the duplicate is visible and cheap to remove at the gate. If a route expands to zero tasks — an empty milestone, a glob matching no plan file, a free-form goal that yields no distinct task — stop and report which route came back empty. Do not continue on the remaining routes as though the invocation were complete: the user asked for that source, and quietly orchestrating a subset produces a close-out report that looks successful while missing the work. ## Free-form decomposition Free-form is the weakest of the five routes above and is treated as such. A ref names a task someone already wrote down, a container is a list the tracker maintains, and a plan carries task boundaries `writing-plans` defined deliberately. A quoted goal carries none of that: where one task ends and the next begins exists only in the orchestrator's reading of one sentence, and there is no ground truth to check that reading against. Two agents given `"fix the parser and clean up auth"` may reasonably produce two tasks or five, and both are defensible. **The split is therefore always surfaced before anything is filed or dispatched.** The proposed task list appears in the Step 5 battle plan with, for each task, the fragment of the original goal it was derived from, so the user can see and correct a decomposition they never wrote. This is not optional for the free-form route and does not depend on how confident the split felt. **When the split is not clear-cut, do not pick one.** A single sentence that could be one task or three, or a conjunction that might be one change or two, goes into the consolidated Step 5 interview as a proposed-answer question offering the candidate splits, with the **coarser** split marked `(Recommended)`. Coarser is the safer default because under-splitting leaves one forge run that can still handle the whole change and spin off the remainder through its own Step 10, whereas over-splitting fabricates boundaries nobody drew: it invents collision edges between halves of one change, opens PRs for fragments that only make sense together, and merging them back after dispatch means discarding provisioned worktrees. **Under `automode`, a free-form split is never filed to the tracker.** `automode` lifts the Step 5 gate, which removes the exact safeguard this route depends on, so the two rules below apply together: 1. the split is recorded as an explicit assumption in the battle plan, named as an assumption rather than presented as a finding; 2. its tasks run under synthetic `T1..Tn` IDs and are **not** filed, unless the user named the split themselves — for example by enumerating the tasks in the goal, or by re-invoking with refs once they have seen the recorded split. This is the one place where forge's "under `automode`, post them directly" is too eager, and the difference is what the issue body contains. Forge's Step 10 drafts spin-off issues from a human-written issue body, so a person authored the thing being described and `automode` is only automating the filing. Here the issue bodies would be the orchestrator's own guess at a boundary nobody stated. An unreviewed guess written into a shared tracker is not reversible by the person who has to clean it up: closing N wrong issues is manual work that lands on someone else's queue, and the notifications have already gone out. A synthetic ID costs nothing, leaves the tracker untouched, and still lets the run proceed — so the safe branch is also the cheap one. ## Materializing refs Step 2's job. Ref and container routes already carry real refs and need nothing further. The plan and free-form routes do not, so they are given real issue refs via **`/to-tickets`**: draft one issue per task, show the drafts, and file them on an explicit user yes. Under `automode`, file the plan route's issues directly, matching forge's Step 10 behavior for spin-off issues — a plan is a human-written document, so the drafted bodies describe boundaries a person actually set. The free-form route is the exception and is never filed under `automode`; see Free-form decomposition above for the rule and its reasoning. Tickets filed this way carry a bonus the synthetic path cannot: `/to-tickets` has each ticket declare the tickets that **block** it, so the filed set arrives with its dependency edges already stated — Step 4 reads them into `declaredBlockers` (see [collision-graph.md](collision-graph.md)) instead of inferring the ordering from file overlap alone. If the user declines filing, assign synthetic IDs `T1..Tn` in task-list order. A synthetic ID is local to this run: it names the task in the battle plan, the ledger, the worktree name and the dispatched forge run, and **it never reaches a tracker on its own**. Any later step that would write to a tracker for such a task — a forge Step 12 write-back, a Jira transition, an issue-closing keyword in a commit or PR body — skips that write and names the skip in the Step 9 close-out. Writing `T3` to a tracker would either fail or, worse, resolve against an unrelated real issue, and the close-out line is what keeps the skipped write from looking like a completed one. That refusal covers automatic write-back. A user can still ask, later, to file a synthetic task explicitly — and that request is not answered the same way for every synthetic ID. Before filing any synthetic task on request, consult its `syntheticIdReason` in the ledger ([ledger.md](ledger.md)'s "Synthetic task IDs"): `declined-filing` may be filed on request, since the task was already human-stated and the user is only reversing an earlier decline; `automode-freeform` is refused until the user has seen and accepted the split, because that split was the orchestrator's own guess, and an unreviewed guess written into a shared tracker is not reversible by whoever has to clean it up. Synthetic IDs are never reused across runs and never renumbered mid-run. `resume` reads them back from the ledger unchanged, since the worktrees and branches provisioned in Step 6 are already named after them. ## Container expansion `milestone`, `epic` and `label` expand through the tracker that forge already supports for this repo, routed exactly as forge's Step 1b/1c routes a single ref: | Tracker | Forge reference to reuse | |---|---| | GitHub (incl. GH Enterprise) | [../../forge/references/trackers/github.md](../../forge/references/trackers/github.md) | | GitLab (incl. self-hosted) | [../../forge/references/trackers/gitlab.md](../../forge/references/trackers/gitlab.md) | | Jira | [../../forge/references/trackers/jira.md](../../forge/references/trackers/jira.md) | | Linear | [../../forge/references/trackers/linear.md](../../forge/references/trackers/linear.md) | Expansion reuses those references' own fetch commands and REST fallbacks; it does not add fetch logic of its own. A container is a list query against the same host and the same auth a single-ref fetch already uses, so a second implementation would drift from forge's on host detection, enterprise URLs and auth failure handling without buying anything. If the tracker cannot be reached or the container cannot be resolved, stop and report it exactly as forge's Step 2 does for an unreachable source, including under `automode`. Guessing at a container's contents would fabricate the task list the entire run is built on. If the tracker is reachable but the container resolves to a single issue, continue with a one-task run and say so once, then let the user decide whether `/forge <ref>` is the better tool. Do not abort: the user's request was well-formed, and the orchestrator degrades to a single dispatched forge run without any loss. -
flags.md 6.3 KB
# Blacksmith — flag matrix The full set of flags the orchestrator understands, with composition rules and conflict handling. Forge's own flags are not repeated here; they pass through, and their matrix stays in [../../forge/references/flags.md](../../forge/references/flags.md). ## Flags Each flag is consumed by the orchestrator and never forwarded to a dispatched forge run. `plan <path>` and `resume` are work-source selectors as well as flags — they occupy the `<work-source>` position in the grammar and are listed here so the matrix is complete; their routing is in [entry-routes.md](entry-routes.md). | Flag | Effect | Detail | |---|---|---| | `afk` | After a 5-minute quiet timeout, self-verify and merge **blocking PRs only**; combined with `automode`, also authorizes each dispatched run's own Step 12 commit-and-PR — the single sanctioned exception to forge's never-auto-push floor | [afk.md](afk.md) | | `resume` | Resume a run from its ledger instead of starting a new one | [ledger.md](ledger.md) | | `budget <n>` | Token ceiling for the run, with a deterministic degradation ladder | [scheduling.md](scheduling.md) | | `strict` | No depth downgrade; every task runs full forge | [triage.md](triage.md) | | `stack` | Blocked tasks on a **soft** edge base off the blocker's branch and open stacked PRs; hard-edge blocks still park | [scheduling.md](scheduling.md) | | `rescout` | Force scout analysis even where dispatch-ready plans exist | [plan-sourced.md](plan-sourced.md) | | `max <n>` | Concurrent implementation agents; default `4` | [scheduling.md](scheduling.md) | | `dry` | Emit the battle plan and stop; dispatch nothing | [battle-plan.md](battle-plan.md) | | `unified` / `split` | Override worktree grouping: `unified` puts a coupled cluster in one worktree behind one PR, `split` gives every task its own | [scheduling.md](scheduling.md) | | `plan <path>` | Source tasks from a written implementation plan; each plan task becomes one orchestration task | [plan-sourced.md](plan-sourced.md) | ## `afk` and the no-auto-push floor Forge states a hard floor that even `automode` does not lift: never auto-commit, auto-push, or write back to a tracker. Orchestrating N runs does not relax it — but "write back to a tracker" means the *source* issue or ticket a task was dispatched for, not every write a tracker will ever see: Step 2 files *new* issues via `/to-tickets` for the plan and free-form routes, including under `automode`, exactly as forge's own Step 10 sanctions filing spin-off issues it did not author. Every dispatched run inherits the source-write floor verbatim, and by default the orchestrator notifies the user that a blocking PR is ready and parks rather than merging; likewise, by default no dispatched run commits or opens a PR at all until the Step 5 battle-plan approval grants it that Step 12 selection — see `SKILL.md`'s Overview and Step 7 for the three-way rule. `afk` is the **single sanctioned exception**, and it is deliberately narrow: it merges only PRs that block another task, only after a self-verification checklist passes in full, and never a terminal PR that nothing waits on — not even under `afk automode`. Combined with `automode`, it also authorizes each dispatched run's own Step 12 commit-and-PR selection where the battle-plan approval could not have granted it, because no human said "yes, forge them" in the first place — see [afk.md](afk.md)'s "Commit-and-PR authorization". It is written down here, in `SKILL.md` and in `afk.md` with its reasoning, so that the exception is auditable rather than a quiet contradiction of the documented floor. Any single failed check keeps the PR parked and notifies; there is no merge retry loop. ## Forge flag pass-through Every flag forge understands passes through unchanged to every dispatched forge run: `automode`, `docs`, `tdd`, `lookup`, `secure`, `changelog`, `ci-watch`, `compress`, `implement`, `codex`, `codex challenge`, `codex impl`, `coderabbit`, and `code-review`. A pass-through flag applies to all tasks in the run; there is no per-task flag syntax. Two of forge's flags are overridden rather than passed through as written: - **`worktree` is always implied and orchestrator-managed.** Step 6 provisions, names and tracks every worktree, and Step 9 reports each one for cleanup, so the decision cannot be delegated to the individual runs. Passing it explicitly is accepted with a one-line note rather than rejected, because the user is asking for behavior that is already in force and an error would be pedantry. - **`automode` lifts the Step 5 battle-plan gate** exactly as it lifts forge's Step 6 gate, and still passes through to each dispatched run. On its own it lifts no floor: without `afk`, no run commits, opens a PR, or writes back to the source tracker, and each run's Step 12 `/goal` verification still has to pass — every dispatched run stops at its own Step 12 plan-only output instead, per the `afk` section above. `afk` combined with `automode` is what grants the commit-and-PR selection; terminal PRs are still left for the user even then. A runtime that cannot honour a pass-through flag degrades exactly as forge does — the reviewer flags `codex`, `codex challenge`, `codex impl` and `coderabbit` are Claude Code only and are ignored with a one-line warning elsewhere. The orchestrator emits that warning once for the run, not once per dispatched task, since N identical warnings tell the user nothing the first one did not. ## Composition rules | Rule | Why | |---|---| | `strict` beats `budget` | Under both, a budget squeeze reduces pool width and defers tasks; it never downgrades depth. | | `dry` makes `afk` and `budget` inert | Nothing is dispatched, so there is nothing to merge or spend. Both are reported as inert in the battle plan rather than silently dropped, so the user can see the flag had no effect. | | `stack` reduces but does not replace `afk` | Stacking removes most parks; `afk` still governs any blocking PR that must actually land. | | `rescout` supersedes plan-sourced dispatch for the tasks it re-scouts | A scout produces its own proposal; the plan is shown as context and any disagreement is surfaced at the gate. | | `unified` / `split` are mutually exclusive | Passing both is an error, not a silent precedence: they ask for opposite PR topologies, so Step 1 stops and reports the conflict instead of picking one and hiding which shape shipped. | -
ledger.md 10.6 KB
# Blacksmith — the run ledger How Step 6 records provisioning before Step 7 dispatches anything, how Step 7 and Step 8 keep it current, and what `resume` reads back — and re-verifies rather than trusts. Loaded by Steps 6, 7 and 9, and by the `resume` flag. ## Manual verification recipe Start a run with four or more tasks across at least two waves. Let wave 1 reach `pr-open` for every one of its tasks, then interrupt the session before wave 2 dispatches. Re-invoke `/blacksmith-orchestrate resume`. Expected: wave 1's PRs are untouched — no new commits, no re-dispatch, no duplicate worktree — because their ledger rows already read `pr-open` or later and the resume contract below never re-dispatches a task past that point; wave 2 is re-planned from the ledger's recorded blockers and dispatches normally once each blocker has relayed, exactly as it would have if the run had never been interrupted. ## Location ``` $(git rev-parse --git-common-dir)/blacksmith/run-<id>.json ``` Inside `.git`, not the working tree, for three reasons that all follow from the same fact: `git rev-parse --git-common-dir` resolves to one shared location regardless of which linked worktree asks. The ledger is therefore shared by every worktree this run provisions rather than living in one of them arbitrarily; it is never committed, because nothing under `.git` is tracked by the repository it governs; and it survives both a branch switch in any worktree and a `/compact` of the agent's own context, since it is a file on disk that neither operation touches. ## Schema ```jsonc { "runId": "2026-08-21-a3f9", "invocation": "/blacksmith-orchestrate 42 43 51 60 automode afk", "flags": { "orchestrator": ["afk"], "forge": ["automode"] }, "grouping": "split", "tasks": [ { "id": "42", "route": "scout", "title": "Parser rewrite", "tier": "opus", "depth": "full", "worktree": "../repo-fix-parser-utf16", "branch": "fix/parser-utf16", "files": ["src/parser.ts", "src/lexer.ts"], "blockers": [], "pr": 101, "state": "pr-open" }, { "id": "43", "route": "plan", "planSlice": "docs/superpowers/plans/2026-08-21-parser.md#task-2", "tier": "sonnet", "depth": "full", "worktree": "../repo-fix-parser-bom", "branch": "fix/parser-bom", "files": ["src/parser.ts"], "blockers": ["42"], "pr": null, "state": "parked" } ], "decisions": [ { "at": "step-4", "note": "43 blocked on 42 — hard overlap on src/parser.ts:parseHeader" } ] } ``` `tier` and `depth` are the exact tokens [triage.md](triage.md) produces — `tier` is `opus`, `sonnet` or `haiku`, `depth` is `full`, `lite` or `patch`, always lowercase. Triage.md's "Opus 5" / "Sonnet 5" / "Haiku 4.5" are the display names those tokens resolve to on this run, not values that ever appear in the ledger; the ledger keys on the token, not the model name a future run of this repo might resolve it to. ### Synthetic task IDs Neither task in the example above is synthetic, so neither shows the one field that only applies to a task whose `id` is a `T1..Tn` value assigned per [entry-routes.md](entry-routes.md): ```jsonc { "id": "T2", "syntheticIdReason": "declined-filing", "...": "..." } ``` `syntheticIdReason` is one of two values, and it exists because the two routes to a synthetic ID carry different restrictions on filing it later — a restriction that is silently lost the moment a resumed run forgets which route produced the ID: | `syntheticIdReason` | How it arose | What `resume` must still refuse | |---|---|---| | `declined-filing` | The user was offered `/to-tickets` filing for a real, human-stated task and declined it — entry-routes.md's "Materializing refs" | Nothing beyond the ordinary rule: a synthetic ID never reaches a tracker on its own, but filing it later, on request, is always available. | | `automode-freeform` | The task came from an `automode` free-form goal split — entry-routes.md's "Free-form decomposition" | Filing outright. The split itself was the orchestrator's own guess, made without the Step 5 gate that would otherwise have shown it to a person; it must not be filed until a human has actually seen the split, not merely until one is asked. | A task with a real ref never carries `syntheticIdReason` — the field's absence is itself meaningful, not an omission. `resume` reads the field back unchanged rather than re-deriving it from the task's route, because the route alone does not distinguish the two cases (both can originate from a free-form task) and re-deriving it risks quietly relaxing the `automode-freeform` restriction on a run that already recorded it correctly the first time. ## Task states ``` planned → provisioned → running → review → pr-open → parked → merged → done ``` `failed` is reachable from any running state — a task's forge run can error, or exhaust [triage.md](triage.md)'s runtime-promotion retries, from `running`, `review` or `pr-open` alike, and the ledger records whichever state it failed out of rather than collapsing the history. The chain above is the common path, not a strict single line: `parked` is reachable from two different points, and both land on the same state because both mean the same thing — this task is waiting on something outside its own dispatched agent's control, and no amount of running that agent again would change it. | State | Meaning | |---|---| | `planned` | In the battle plan; no worktree exists yet. | | `provisioned` | Step 6 created the worktree and wrote this row; no agent has been dispatched. | | `running` | Step 7 dispatched the agent; its forge run is in flight, before the review loop. | | `review` | The dispatched run's own forge Step 8 review loop is in progress. | | `pr-open` | The dispatched run opened a PR (or, without a host CLI, a branch — [relay.md](relay.md)'s "Without a host CLI"). | | `parked` | Reached from `provisioned` when the task has an unreleased blocker and its wave has not opened yet ([scheduling.md](scheduling.md)'s wave gating); or reached from `pr-open` when the PR blocks another task and is waiting on merge authority — the default notify-and-wait, or `afk`'s 5-minute quiet timeout ([afk.md](afk.md)). | | `merged` | The PR merged, or the branch landed on the base, and [relay.md](relay.md)'s ancestor proof passed for anything that depended on it. | | `done` | Closed out: nothing further depends on this task, and it is reported as finished at Step 9. | | `failed` | The dispatched run errored, or failed its `/goal` past [triage.md](triage.md)'s one runtime-promotion retry, and parked for the user rather than looping. | ### `pr-open` and `merged` under `automode` without `afk` Under `automode` without `afk`, `pr-open` and `merged` are unreachable states for every task in the run. Per the three-way commit-and-PR rule stated in `SKILL.md`'s Overview and Step 7, no dispatched run's own Step 12 ever commits or opens a PR in that combination — every run stops at its plan-only output, exactly as a standalone `automode` forge run does. A task that completes under this combination is recorded `done` directly from `review` (or from `running`, at a depth that skips review), with `pr: null` and its plan-file path standing in for the PR the schema above shows; Step 9's close-out, not the state name, is what carries the caveat that nothing was pushed. A task with an unreleased blocker under the same combination never reaches `pr-open` either, for a different reason: [relay.md](relay.md)'s "Without a landable commit" section — there is nothing for the relay to prove ancestry against, so it stays `parked` for the remainder of the run instead of resolving. ## Resume contract `/blacksmith-orchestrate resume [run-id]` rebuilds wave state, worktree paths and PR numbers from the ledger, but it **re-verifies before continuing rather than trusting the file** — the file is a record of what Step 6 and Step 7 believed was true when they last wrote it, not a live view of the worktrees or the host: - Every worktree a `provisioned` or later task names must still exist and still sit on its recorded branch. A worktree that has vanished — removed by the user, by another tool, or by a cleanup the user ran manually — puts that task back to `planned`, on the same conservative footing as a task resume has never touched, rather than assuming the missing directory means the work is somehow further along. - Every `pr-open` task's PR state is re-fetched from the host rather than read off the ledger's last snapshot, because the PR may have merged, closed, or gained CI results since the ledger was last written. - `resume` never re-dispatches a task already at `merged` or `done` — re-running a task past those states would either duplicate a shipped change or spend an agent on a task the run had already finished with. - A task's `syntheticIdReason`, where present, carries over unchanged; see "Synthetic task IDs" above for why it must not be re-derived. **A run id that does not resolve to a ledger is not a fresh-run request.** `resume <run-id>` names one specific ledger file; if no `run-<run-id>.json` exists under the location above, the orchestrator stops and reports that the id does not resolve, listing whatever run ids *are* present as candidates, rather than silently falling through to starting a new run under that id — a resume that quietly became a fresh run would provision a second, unrelated set of worktrees under a name the user believed already pointed at the run they meant to continue, and the mistake would not surface until the two runs' branches collided. The same stop applies, for the same reason, to a ledger file that exists but fails to parse: a corrupt ledger is data that cannot be trusted, not evidence the run never started, so the orchestrator does not attempt to reconstruct partial state from it — the file is reported as unreadable and resume goes no further. **A bare `resume` with more than one candidate ledger is ambiguous, not resolved**, in exactly the sense [plan-sourced.md](plan-sourced.md) uses that word for a plan-tier glob that matches several files: guessing which run the user meant risks resuming the wrong one and dispatching into worktrees for tasks nobody is waiting on right now. List every candidate — run id, original invocation, and current wave/state summary — and ask which one to resume. This stop is not lifted by `automode`: choosing among several plausible runs on no further information is the same class of guess Step 1 already refuses to make when a route resolves to nothing, and `automode` lifts approval gates, not the disambiguation this skill was never licensed to perform silently. A bare `resume` with exactly one candidate ledger resumes it without asking, since there is nothing left to disambiguate. -
plan-sourced.md 9.6 KB
# Blacksmith — the plan-sourced analysis route How Step 3 turns a written implementation plan into forge dispatches without spawning a scout for any task the plan already answers. Loaded by Step 3 and by the `rescout` flag row. ## Manual verification recipe ``` /blacksmith-orchestrate plan docs/superpowers/plans/2026-08-21-auth.md dry ``` Expected against a fresh plan (paths current, base SHA matching `HEAD`): zero scout agents spawned. Every task in the battle plan is annotated `route: plan-sourced` and dispatches through `/forge plan <path>#<task-heading-slug>`. ``` /blacksmith-orchestrate plan docs/superpowers/plans/2020-01-01-stale-example.md dry ``` Expected against a plan naming a path that no longer exists: the affected task is flagged **stale** on the battle plan with the diverged path named, and Step 3 routes that task to a scout instead — the rest of the plan's tasks, if their own paths are still current, stay plan-sourced. ## Detection order A task's plan source is resolved in this order, and the first hit wins: 1. An explicit `plan <path|glob>` argument in the invocation. 2. `docs/superpowers/plans/*.md`. 3. `docs/superpowers/specs/*-design.md`. 4. A plan location named by the repo's own `CLAUDE.md` / `AGENTS.md` — an explicit path or glob the guide states. If the guide names a location that resolves to no existing file or directory, this tier is skipped with a one-line note rather than treated as a match; an unresolvable configured path degrades, it does not stall the run. A task that matches none of these has no plan source and goes straight to the Step 3b scout fan-out; that is not a failure, it is the expected outcome for refs, containers, and free-form tasks that never claimed a plan. **A tier that matches more than one file is ambiguous, not resolved.** Tiers 2–4 are globs over directories that normally hold many files once a few features have shipped, so matching several plans is a routine outcome, not an edge case. Ambiguity is not-dispatch-ready, and not-dispatch-ready means scout: the orchestrator does not guess which of several candidates governs this task. It lists the candidate files and asks which plan applies, in the Step 5 proposed-answer format. Under `automode`, where it cannot ask: if exactly one candidate contains a task that corresponds to the work source, use it and record the assumption on the battle plan; otherwise fall back to Step 3b scouting for that task. Falling back costs one scout run; guessing wrong costs implementing a plan written for different work — the same trade the dispatch-ready ladder already makes everywhere else: when a cheap check cannot establish an answer, the fallback is to scout, not to improvise. ## Task boundaries come from the plan `writing-plans` defines a task as "the smallest unit that carries its own test cycle and is worth a fresh reviewer's gate." The orchestrator does not re-decompose a plan's task list against that or any other rule — the plan author already drew the boundaries, and a plan is exactly the case in Step 1's grammar where boundary-drawing is not the orchestrator's job. Slice addressing follows forge's own plan-entry grammar: `<path>#<task-heading-slug>` for one task within a multi-task plan, or the whole file for a single-task plan. ## Dispatch-ready check A task is dispatch-ready — eligible to skip the scout and go straight to `/forge plan <path>` — only when all three hold: 1. The plan slice names a concrete file list for the task (not "various files" or an unstated scope). Naming a list is necessary but not sufficient on its own — whether those paths still exist on disk *today* is a separate, ongoing check; see the Freshness guard below. 2. The plan slice states pass criteria for the task — `Run:`/`Expected:` lines or an equivalently concrete deliverable. 3. The plan slice's scope boundary is unambiguous — nothing in the task's own description implies work outside the file list in condition 1. Each condition can fail independently, and each failure degrades differently rather than falling back to a full scout by default: | Missing | Degradation | |---|---| | File list | Cheap hydration: a targeted grep pass scoped to that task alone — not a scout, not a repo sweep — to recover a file list before dispatch. | | Pass criteria | The orchestrator derives pass criteria from the task's own text and confirms the derivation at the Step 5 gate. This never skips confirmation, because `/goal` is a hard floor forge refuses to proceed without, and a guessed `/goal` that nobody confirmed is worse than one that cost a gate round-trip. | | Scope boundary | Full scout. An ambiguous boundary is not a gap a grep pass or a derived criterion can safely close — the orchestrator cannot tell what it doesn't know is out of scope, so only a forge Part 1 pass, run against the live tree, resolves it. | This is the same situation forge's own `plan-entry.md` describes as the **not-dispatch-ready fallback**, seen from the other caller: standalone `/forge plan <path>` falls back to its own Part 1 (Steps 4–6) when a slice fails a Step P check; here the orchestrator falls back to scouting that task instead. Both rules exist because implementing on a check that failed is exactly the failure the check exists to catch, and both resolve the same way — stop dispatching against the unverified assumption, spend one cheap pass to ground it, and only then proceed. Nothing in either file should be read as contradicting the other; they are the same rule with the caller swapped. ## Plan task order is a declared dependency edge A plan's task list is assumed to be a blocker chain — task 2 depends on task 1 having landed — unless one of two conditions lifts the assumption: - The plan explicitly marks tasks independent, or - Two tasks' file sets are disjoint **and** the plan's File Structure section shows no shared interface between them. Both conditions must hold for the second case; disjoint file sets alone are not enough; a shared interface described only in prose and not the file list still creates a real edge that a `filesToTouch` diff would miss. ## Triage inputs for a plan-sourced task A plan-sourced task never runs a scout, so `difficulty`, `blastRadius` and `symbols` are never populated the scouted way — and [triage.md](triage.md)'s and [collision-graph.md](collision-graph.md)'s undeterminable defaults are not meant to be the common case for a route that exists specifically to be cheap. The orchestrator grades both triage axes and lists the symbols from the slice text itself before Step 4 sees the task: the files the slice already names (condition 1 of the dispatch-ready check above) for file count and symbol candidates, the plan's own File Structure section and prose for new-interface and algorithmic-content signals that inform `difficulty`, and the surface the slice's deliverable touches — user-facing, auth/payment-adjacent, public API, migration, existing test coverage — for `blastRadius`. This is the same grading [triage.md](triage.md) already describes, done by reading the plan instead of by dispatching a scout to rediscover it. The undeterminable defaults — `difficulty: high`, `blastRadius: high`, `symbols: []` — apply to a plan-sourced task only when the slice genuinely does not say, the same standard the dispatch-ready check above already applies to a missing file list or missing pass criteria: silence in the text, not merely the absence of a scout run. Grading every plan-sourced task to the undeterminable defaults by default would put every one of them at `opus × full` with every edge hard, which defeats the entire reason this route exists — a written plan is supposed to be *cheaper* to dispatch from than a scout, not more conservatively triaged than one. ## Freshness guard A plan-sourced task's dispatch-readiness is void the moment its plan slice no longer describes the tree it will run against: - Every path the slice names must still exist. - The slice's stamp — a recorded base SHA where the plan states one, otherwise the plan file's own last commit — is compared against current `HEAD` for the paths the slice names. - Any changed path in that comparison flags the task **stale**. A stale task falls back to scouting rather than dispatching against a plan written for a tree state that has since moved; scouting rebuilds the file list and scope from the live tree instead of trusting a description that stopped matching it. **When no base SHA can be determined at all** — no `Base:` line in the plan, and the plan file itself has no commit yet — the second bullet has nothing to compare against. This is not an exotic case; it is the *most common* one, because the natural workflow is brainstorm → write plan → orchestrate in a single session, before the plan file is ever committed. A slice in this state cannot be *proven* fresh, but it must not be treated as **stale** either — blocking here would break the exact workflow this route exists to serve. Fall back to the path-existence check alone (the first bullet), record the gap on the battle plan as a stated assumption — `freshness unverified: plan not yet committed` — and proceed. An uncommitted plan is almost always one just written, so the risk the stamp check guards against, a plan written against a tree state that has since moved, barely applies here; surfacing the assumption costs one line on the battle plan, while blocking costs the user their whole flow. This is forge's own `docs` stamp check — the same staleness test `/forge plan <path>` runs against a single slice at its own Step P, including its own no-determinable-base-SHA fallback — applied here across a set of tasks before any of them is scheduled. -
relay.md 5.9 KB
# Blacksmith — the relay How a blocked task's park ends: the proof a blocker's changes are actually reachable from the dependent's base, and the rebase that follows it. Loaded by Step 8, and by [scheduling.md](scheduling.md) whenever it needs to know when a blocked task's wave is recomputed. ## Manual verification recipe A blocking PR merged by squash. Expected: the SHA ancestor check (`git merge-base --is-ancestor <blocker-head-sha> origin/<base>`) fails, because squash rewrites the head SHA into a new commit that never existed on the blocker's branch; the `git log --grep` fallback then succeeds, matching the PR number in the squash commit's message on `origin/<base>`; and the relay releases on that positive proof. ## The relay When a blocker's PR merges, the dependent is not released on the PR page's say-so. A merged label on a host UI is a claim about the host's state, not a proof about the dependent's worktree, and the two can disagree — the base can move again before the dependent rebases, or the merge can be reverted. The orchestrator proves the code is actually present on the base the dependent is about to build on: ```sh git -C <dependent-worktree> fetch origin git -C <dependent-worktree> merge-base --is-ancestor <blocker-head-sha> origin/<base> git -C <dependent-worktree> rebase origin/<base> ``` If the rebase stops on conflicts, the dependent's dispatched agent resolves them through **`/resolving-merge-conflicts`** — hunk by hunk, by intent traced to each side's primary source, finishing the rebase rather than `--abort`ing — before the relay proceeds. An aborted rebase is a relay that never released, and an ad-hoc resolution is exactly the silent semantic drift the ancestor proof exists to prevent. A non-ancestor result does **not** release the relay — it means the blocker's commit is not reachable from the base yet, whatever the host UI claims, and the dependent stays parked. Squash-merges and rebase-merges both rewrite SHAs, so a squash- or rebase-merged blocker will *always* fail the ancestor check even after a real, successful merge — that is expected, not a signal of a stuck relay, and it is exactly why the ancestor check is not the only proof this mechanism has. The fallback proof is a `git log --grep` for the PR number on the base, plus a content assertion drawn from the blocker's proposal — a symbol or line the blocker was specified to introduce, checked for its presence on the base. Only a positive proof — the ancestor check, or the grep-plus-content fallback — releases the relay; a negative or inconclusive result on both leaves the task parked and re-checked on the next poll, exactly as an unmerged PR would. A blocking PR closed without merging never produces a positive proof by either path: there is no commit to be an ancestor, and no merge commit for the grep to match on the base. The dependent stays parked, and the park is reported at close-out as blocked on a PR that closed unmerged — this is not treated as a relay failure to retry, because there is nothing left to retry: closing a blocking PR without merging it is the blocker task not completing, and the fix is for a human to reopen it, supersede it, or drop the dependent, not for the orchestrator to keep polling a proof that structurally cannot resolve. A blocker branch that was force-pushed after the dependent already rebased onto an earlier state of it is caught by the same ancestor check, run again: the dependent's next relay attempt fetches the current `origin/<base>` and re-proves ancestry against whatever is there now, so a force-push that dropped the commit the dependent built on shows up as the ancestor check failing on a commit it previously passed. This is the same conservative default this graph applies everywhere information is missing or has changed underneath it — a stale rebase is not assumed still valid, it is re-proven every time the relay is asked to release. ## Parking Parked is not blocked-forever: every unblocked wave keeps running while a task parks. A parked task is waiting on one specific proof, not on the whole run, so its wave being idle never idles any other wave — [scheduling.md](scheduling.md)'s wave gating already treats this as the ordinary case, not a stall. ## Without a host CLI If `gh` or `glab` is absent or unauthenticated, no PRs are opened at all. Components produce local branches instead, the ancestor proof runs against the local base branch — `<local-base>` in place of `origin/<base>` in the commands above, since there is no remote host state to fetch — and close-out reports the branches for the user to publish. `afk` is inert in this mode and says so: there is no PR for it to notify about, self-verify, or merge, since `afk`'s entire mechanism operates on a host PR, and a host with no CLI at all offers `afk` nothing to act on. ## Without a landable commit (`automode`, no `afk`) Under the three-way commit-and-PR rule stated in `SKILL.md`'s Overview and Step 7, an `automode` run without `afk` never reaches its own Step 12 commit: every dispatched run stops at a plan-only file, exactly as a standalone `automode` forge run does. That leaves a blocked task's relay with nothing to prove ancestry against — no commit exists anywhere the blocker's worktree could be fetched from, so the ancestor check and its grep-plus-content fallback both have no target to test, not merely an unmet one. A dependent task behind a blocker dispatched under this combination stays `parked` for the remainder of the run: this is not a stall the relay is expected to resolve later, and it is not a relay failure to retry the way a closed-without-merging blocking PR is — it is the reported outcome of the flag combination itself, named explicitly at close-out by ref rather than discovered by the user only when nothing ever un-parks. See [ledger.md](ledger.md)'s "`pr-open` and `merged` under `automode` without `afk`" for the matching ledger-state consequence. -
scheduling.md 6.7 KB
# Blacksmith — scheduling: worktree topology, waves, budget How Step 4 turns the oriented, graded collision graph from [collision-graph.md](collision-graph.md) into worktree groups and dispatch waves, and how Step 6 provisions from that plan. Loaded by Steps 4 and 6. ## Manual verification recipe Four tasks, one hard edge between two of them (the other two are unconnected to anything). Expected: three connected components — the hard-edge pair, and the two singletons — so three worktrees. The hard-edge pair's dependent is blocked, so wave 1 dispatches the two singletons plus the hard-edge pair's blocker: three worktrees in wave 1. The hard-edge pair's dependent enters wave 2, once its blocker has relayed. ## Grouping The connected components of the collision graph are the unit — tasks in different components share no files by construction, since an edge is exactly what would put them in the same component, and the graph has no other way to connect two tasks. Component membership is read straight off the graph Step 4 already built; scheduling does not recompute file overlap or re-derive an edge, it only asks which tasks a hard or soft edge — of either kind, overlap-derived or stated — puts in the same component. | Component shape | Topology | |---|---| | one task | own worktree, own branch, own PR | | two tasks, tightly coupled, same kind | **unified** — one worktree, tasks run sequentially as ordinary commits on one branch, one PR | | larger | **split** — one worktree per task, with the [relay](relay.md) | Unification is the cheap win: it converts an entire park → notify → merge → rebase cycle into two sequential commits in the same worktree, with no PR-to-PR relay in between. Splitting is the default past two tasks because six colliding tasks landing in one PR is a bad review artifact, even though it would merge more easily than six separate ones — a reviewer cannot usefully hold six tasks' worth of intent in one diff, and the relay's per-edge granularity is what keeps a large component reviewable at all. `unified` and `split` force the choice globally, overriding the shape rule above for every component in the run, not just the one that prompted the flag. A component larger than `max` is not held back from dispatch on that basis alone — `max` bounds wave-1 *concurrency*, not component size, and a component is one unit that either all dispatches together (unified) or fans out across a wave with its own internal ordering (split); a single component's tasks compete for the same `max` concurrency slots as every other component's, exactly like any other tasks. What a component larger than `max` changes is how many of its own tasks can be in wave 1 at once: if a five-task split component has no internal blockers, at most `max` of its five dispatch together and the rest wait for a slot in the same wave's rolling window, not for a later wave — a component is never treated as blocked by its own size. `unified` forced on a component whose tasks are different kinds is not silently honored as though the component still matched the middle row of the table above — the table's `unified` row describes "two tasks, tightly coupled, same kind" for a reason: unifying two different-kind tasks (for example a docs task and a parser rewrite) into one worktree and one PR would put unrelated intents behind a single review, exactly the bad-review-artifact outcome splitting exists to avoid, just at component size two instead of six. The forced unification still happens — `unified` forces the choice globally, as stated above — but the battle plan names the component as `unified (mixed kind)` so the mismatch is visible at the gate rather than looking like an ordinary same-kind pairing the user would have chosen anyway. ## Waves Tasks with no unsatisfied blockers form wave 1 and dispatch in parallel up to `max` (default `4`). A task enters a later wave when every blocker it depends on has released its relay — not when the blocker's PR merges, and not when the blocker's wave finishes; release is [relay.md](relay.md)'s ancestor proof passing, which can happen after the blocker's PR merges or, without a host CLI, after its branch lands on the local base. Wave gating reads only hard versus soft off each edge, never which of the three mechanisms in [collision-graph.md](collision-graph.md) produced it — file overlap, `declaredBlockers`, or plan task order are all the same to scheduling once the graph has graded and oriented the edge. A hard edge parks the dependent until release; a soft edge parks it too under the default schedule (soft is a conservative default, not a free pass to parallelize), unless `stack` is set, in which case the dependent dispatches in the same wave as its blocker, based off the blocker's branch, with its PR opened stacked on the blocker's. A wave where every task is parked is not a stall condition scheduling treats specially — it is the ordinary result of every remaining task having an unreleased blocker at that point in the run, and it resolves itself the instant one of those blockers relays and its dependent's wave is recomputed. There is nothing to dispatch in such a wave and nothing is dispatched; the run's forward progress that tick is at the blocker still in flight, not at scheduling, so this is not reported as an error or a degraded state — only the ordinary case of Parking in [relay.md](relay.md) applies, unchanged: parked is not blocked-forever, and any *other* wave with an unblocked task keeps running regardless of how many tasks are parked in this one. ## Budget degradation ladder `budget <n>` applies a token ceiling to the run, in this order: 1. reduce pool width toward 1 — costs wall-clock, not quality; 2. defer the lowest-priority remaining tasks to a follow-up run and report them explicitly; 3. only if `strict` is not set, downgrade `full → lite` on low-blast-radius tasks; 4. never cross the four floors in [triage.md](triage.md). If the budget is exhausted mid-run with tasks still unstarted after all four rungs have been applied — pool width is already 1, every deferrable task is deferred, every eligible downgrade has been taken, and the floors still block going further — the run does not stop silently or attempt to squeeze a floor to finish the batch. The remaining unstarted tasks are deferred exactly as rung 2 already defers tasks, named individually in the close-out report, and the run closes out with whatever wave is in flight left to finish under its already-committed budget. A task already dispatched when the budget runs out is not aborted mid-flight; degradation only ever changes what starts next, never what is already running. Silent truncation is forbidden: anything deferred or downgraded by this ladder is named in the close-out report, by ref, with which rung deferred or downgraded it. -
triage.md 7.3 KB
# Blacksmith — triage How Step 4 grades each task on two independent axes — implementer tier and forge depth — before scheduling ever sees a single task. Consumes Step 3's `difficulty` and `blastRadius` fields; produces the tier and depth values Step 6's ledger and Step 7's dispatch key on. Loaded by Step 4. ## Manual verification recipe **Case 1 — trivial but dangerous.** A one-line change to an auth check: `difficulty: low` (one line, existing pattern), `blastRadius: high` (auth adjacency). Expected: `haiku × full`. Low difficulty picks Haiku 4.5, but high blast radius floors the depth at `full` regardless of what difficulty says; never `lite`, never `patch`. **Case 2 — hard but contained.** A multi-file parser rewrite: many files, algorithmic content, several open risks, no auth/payment/public-API adjacency, but a wide caller count on the symbols it touches. Expected: `opus × full`. High difficulty picks Opus 5; the caller count alone is enough blast-radius signal to floor the depth at `full` too, so both axes agree here even though they were graded independently. **Case 3 — trivial and contained.** A docs typo: one line, no new interface, no user-facing surface, no callers. Expected: `haiku × patch`. Both axes clear to their lowest grade, so the depth downgrades all the way to `patch` and Haiku 4.5 implements it with the orchestrator reading the diff itself. Two independent axes. They correlate but are not the same: a one-line change to an auth check is trivial to implement and catastrophic to get wrong. ## Axis 1 — difficulty → model tier Signals: file count, new interface vs existing pattern, algorithmic content, number of open risks. | Difficulty | Agent model | Under `codex impl` pass-through | |---|---|---| | high | Opus 5 | `gpt-5.6-sol`, effort `high` | | medium | Sonnet 5 | `gpt-5.6-terra`, effort `xhigh` | | low | Haiku 4.5 | `gpt-5.6-luna`, effort `xhigh` | The three rows above map onto the canonical tier tokens `opus`, `sonnet`, `haiku` — the exact strings Step 7 dispatch and the Step 6 ledger schema key on; `Opus 5` / `Sonnet 5` / `Haiku 4.5` name which agent model each token resolves to on this run. `difficulty` is scout-supplied and can arrive absent, or carrying a value outside `high`/`medium`/`low`. Missing or malformed data is never read as an implicit "safe to run cheap": the safe branch is the conservative one, so a task with no usable `difficulty` is graded `high` — top tier — until a human overrides it, and the battle plan surfaces `difficulty undeterminable: <ref>` so that default is visible at the gate rather than silently assumed. A returned `difficulty` that visibly contradicts its own evidence — `low` on a task whose file list runs to dozens of files, or that introduces a new public interface — is not silently corrected either: Step 4 does not second-guess the scout's classification on its own authority, any more than it silently resolves a stated-edge cycle in the collision graph. It flags the mismatch on the battle plan (`difficulty flagged: low vs <n> files touched`) and leaves the resolution to the user at the gate. ## Axis 2 — blast radius → forge depth Signals: user-facing surface, auth/payment/security adjacency, public API or migration, caller count of touched symbols, existing test coverage. | Depth | Forge steps run | Review | |---|---|---| | `full` | 1–12, or P + 7–12 plan-sourced | loop to convergence, normal pass cap | | `lite` | 7, 8, 12 | one review pass, project reviewers only; skips 9, 10, 11 | | `patch` | 7, 12 | orchestrator reads the diff itself; no reviewer subagent | `blastRadius` is subject to the same rule as `difficulty`. Missing or out-of-enum `blastRadius` grades to `high` — forcing `full` depth via floor 2 below — until the user overrides it, surfaced as `blastRadius undeterminable: <ref>` on the battle plan; grading to `high` is the only default that cannot silently under-review a task, since `full` is a strict superset of what `lite` and `patch` run. A returned `low` that contradicts strong evidence — auth or payment adjacency, a public API touched, zero existing test coverage on a wide caller count — is flagged the same way (`blastRadius flagged: low vs <signal>`) rather than corrected in place. ## The four floors 1. **Step 12 `/goal` verification runs at every depth.** `patch` means "verified without a review loop," never "unverified." Forge's hard floor is inherited unchanged. 2. **High blast radius can never be assigned `lite` or `patch`,** whatever difficulty says. This is the entire reason the axes are separate. 3. **Rigor-increasing flags are sticky.** `secure` and `tdd` survive a depth downgrade. 4. **The implementer is never the only reviewer.** Generalized from `codex impl`: a Haiku-implemented task is reviewed at Sonnet or above. A different model family reviews wherever the host makes one available; a same-family distinct instance is the degraded fallback for when it does not — never the preferred choice, and never the same instance that implemented. [../../forge/references/modes/codex-impl.md](../../forge/references/modes/codex-impl.md) is the mode where a different family is guaranteed by construction — this floor is the same rule generalized to a pool where no such guarantee holds. The fallback is not a preference among equals — it is what floor 4 drops to only when the host offers no second family at all. When the host offers only one model family, there is no different family available to require, so the review still runs, at the same family but a distinct agent instance from the one that implemented, exactly as the fallback clause requires. A single-model-family host is the fallback case of floor 4, not an exception to it — it never licenses skipping the review or letting the implementer review its own diff. ## Runtime promotion Triage is a prediction, so it self-corrects. If a `patch` or `lite` task fails its `/goal`, or its diff escapes the file list its plan declared, the orchestrator promotes it one depth and re-dispatches once, bumping the tier if the failure looks like a capability limit. A task that fails after promotion parks for the user rather than looping. Both axes have a ceiling this mechanism runs into. A task already at `full` has nowhere to promote: a `/goal` failure at `full` is handled by the ordinary Step 8 review loop already running at that depth, under its own pass cap, not by this mechanism — if that loop caps out without converging, it parks for the user, the same outcome promotion produces, reached by the review loop's own rule rather than this one. A task already at the top tier (Opus 5) that fails for what looks like a capability limit has no tier left to bump into either: the orchestrator re-dispatches it once more at the same tier with the failure appended to context, and if it fails again it parks for the user — the fails-twice-parks rule holds even when one of the two dimensions a retry could move on is already exhausted. `strict` disables the depth *downgrade* triage would otherwise apply; it says nothing about promotion, because under `strict` every task starts at `full`, so the `patch`-or-`lite` failure that triggers promotion can never occur in the first place. There is no case where `strict` is left holding a task down that this mechanism would otherwise have raised — the failure branch it would interact with never fires.
-
-
scripts
-
scout-fanout.mjs 2.6 KB · in bundle
-
-
SKILL.md 24.4 KB
--- name: blacksmith-orchestrate description: "Orchestrate many forge runs at once: analyze which tasks collide on the same files, schedule the colliding ones sequentially, and run the rest in parallel worktrees at the right model tier and review depth. Use when the user runs /blacksmith-orchestrate or asks to ship several issues, a milestone, or a written implementation plan in one go." --- # Blacksmith Orchestrate > Decide **which tasks run, in what order, at what depth, by which model, in which worktree** — then dispatch one forge run per task. ## Overview This is a wrapper around `forge`, not a fork of it. It never reimplements a forge step: every task it dispatches runs the ordinary twelve-step forge workflow, and everything this skill adds is a decision *about* those runs — which ones may run at the same time, how deep each one goes, and where each one's work lives. Nine numbered steps, split by one gate, deliberately mirroring forge's shape: ``` Part 1 — Plan (1–5): normalize → materialize → analyze → schedule → [GATE] battle plan Part 2 — Execute (6–9): provision → dispatch waves → relay → close-out gate held: no worktree is created and no forge Part 2 runs until "yes, forge them" ``` | Step | Action | |---|---| | 1 | Parse the invocation; split orchestrator flags from pass-through forge flags; route the work source into a canonical task list | | 2 | Materialize refs — spec-file and free-form routes get real issue refs via `/to-tickets`, or synthetic `T1..Tn` IDs if the user declines filing | | 3 | Analyze — route each task to the plan-sourced path or the scout fan-out | | 4 | Build the collision graph, triage matrix and worktree grouping → scheduling waves | | 5 | **[GATE]** one battle plan, one approval | | 6 | Provision worktrees; write the run ledger | | 7 | Dispatch waves — one agent per task at its assigned model tier and forge depth | | 8 | Relay — prove a blocker's changes are on the dependent's base, rebase, release | | 9 | Close-out — aggregate report, PR list, worktree cleanup reminders, self-evolution | Forge's hard floors are inherited unchanged, and orchestrating many runs never relaxes them: every run's Step 12 `/goal` verification still has to pass before that run assembles a commit, and no dispatched run ever writes back to the *source* issue or ticket it was dispatched for — that stays with the user under every flag combination, `afk` included. That is narrower than "never writes to a tracker": Step 2 files *new* issues via `/to-tickets` for the plan and free-form routes, including under `automode`, exactly as forge's own Step 10 sanctions filing spin-off issues it did not author — see [references/entry-routes.md](references/entry-routes.md). Whether a dispatched run may also commit and open a PR for its own task is a separate question, resolved three ways: by default, the Step 5 "yes, forge them" approval *is* the Step 12 commit-and-PR selection forge requires, granted once for every task the approved plan named (see Step 7); under `automode` without `afk`, nobody made that selection, so every dispatched run stops at its own Step 12 plan-only output, exactly as a standalone `automode` forge run does, and nothing is pushed; `afk` is the single sanctioned exception, authorizing unattended commit-and-PR as well as the blocking-PR merge it already governs — it is labelled that way everywhere it appears, so the exception stays auditable instead of becoming a quiet contradiction. Terminal PRs and worktree cleanup stay with the user under every flag combination, `afk` included. "The agent" means whatever agent runs this skill, and "one agent per task" means whatever subagent or parallel-run mechanism the runtime exposes. Adapt every reference — config directory, agent guide, interview UI, host CLI — to your runtime, exactly as forge does. If the runtime cannot run work in parallel at all, say so and fall back to running the waves sequentially rather than pretending to fan out. ## Runtime This skill is Claude-Code-first, degrading elsewhere in the same way forge's `codex` and `coderabbit` flags do: each surface below is used where the host provides it, and the workflow still completes, one step slower, where it does not. | Surface | Used for | Why | |---|---|---| | Workflow tool | Step 3b scout fan-out | bounded parallel fan-out, schema-validated returns, keeps N proposals out of the orchestrator's context | | Agent tool | Step 7 implementation dispatch | per-agent model selection, and the human gates and multi-hour parks live in the main loop | | Ledger file | Steps 6–9 state | survives `/compact`, crash and resume | This is the same runtime-parallelism degradation the Overview already states — "If the runtime cannot run work in parallel at all, say so and fall back to running \[...\] sequentially rather than pretending to fan out." This section does not restate that rule independently; it is the same rule applied to the three Claude-Code surfaces above. Concretely here: on a non-Claude-Code runtime the skill degrades with a one-line warning to sequential forge runs in dependency order, one worktree per component, no `afk`, and inline analysis instead of a scout fan-out. ## Parameters Parse the invocation as `/blacksmith-orchestrate <work-source> [orchestrator-flags] [forge-flags]`. Flag words are orthogonal and can appear anywhere in the request, exactly as in forge. The `<work-source>` is one of five routes — one or more issue refs, a tracker container such as a milestone or epic or label, `plan <path>`, a quoted free-form goal, or `resume` — and routes may combine in a single invocation, so every task carries the route it arrived by. Read **[references/entry-routes.md](references/entry-routes.md)** for the route table, how the routes normalize into one task list, and how containers expand. Full flag matrix (effects, composition rules, conflicts): **[references/flags.md](references/flags.md)**. ### Orchestrator flags These are consumed by the orchestrator and are never passed through to a forge run. Each row's detail file owns that flag's behavior. | Flag | Effect | Detail | |---|---|---| | `afk` | After a 5-minute quiet timeout, self-verify and merge **blocking PRs only** — the single sanctioned exception to forge's never-auto-push floor | [references/afk.md](references/afk.md) | | `resume` | Resume a run from its ledger instead of starting a new one | [references/ledger.md](references/ledger.md) | | `budget <n>` | Token ceiling for the run, with a deterministic degradation ladder | [references/scheduling.md](references/scheduling.md) | | `strict` | No depth downgrade; every task runs full forge | [references/triage.md](references/triage.md) | | `stack` | Blocked tasks on a **soft** edge base off the blocker's branch and open stacked PRs; hard-edge blocks still park | [references/scheduling.md](references/scheduling.md) | | `rescout` | Force scout analysis even where dispatch-ready plans exist | [references/plan-sourced.md](references/plan-sourced.md) | | `max <n>` | Concurrent implementation agents; default `4` | [references/scheduling.md](references/scheduling.md) | | `dry` | Emit the battle plan and stop; dispatch nothing | [references/battle-plan.md](references/battle-plan.md) | | `unified` / `split` | Override worktree grouping: `unified` puts a coupled cluster in one worktree behind one PR, `split` gives every task its own | [references/scheduling.md](references/scheduling.md) | | `plan <path>` | Source tasks from a written implementation plan; each plan task becomes one orchestration task | [references/plan-sourced.md](references/plan-sourced.md) | Every other flag forge understands passes through unchanged to every dispatched run: `automode`, `docs`, `tdd`, `lookup`, `secure`, `changelog`, `ci-watch`, `compress`, `implement`, `codex`, `codex challenge`, `codex impl`, `coderabbit`, and `code-review`. Two of forge's flags are overridden rather than passed through as written. `worktree` is always implied and orchestrator-managed, because Step 6 provisions, names and tracks every worktree itself and a run-level worktree decision cannot be delegated to the individual runs; passing it explicitly is accepted with a one-line note rather than treated as an error, since the user is asking for what already happens. `automode` lifts the Step 5 battle-plan gate exactly as it lifts forge's Step 6 gate, and still passes through to each dispatched run; it does not lift any hard floor named in the Overview. ## When to Use - `/blacksmith-orchestrate <refs>` with two or more issue or ticket refs. - "ship this milestone", "implement this plan", "fix these five issues". - A written implementation plan whose tasks should all land in one sitting. - Any request to run several forge-shaped pieces of work together, where the ordering between them matters. - Work **too large even for one battle plan** — more than one agent session can hold — goes to **`/wayfinder`** first: it maps the destination as decision tickets on the tracker, and the resulting ticket set comes back into this skill through the ordinary refs or container route, blocking edges included. **Don't use** when: - A single issue, ticket or PR is in scope. Use `/forge <ref>`: with one task there is no collision graph, no wave to schedule and no topology to choose, so the wrapper adds a second gate and buys nothing. - No work source is given. Ask which issues, milestone or plan is meant rather than inferring a task list from the repo, for the reason in Step 1. ## Step 1 — Parse the invocation and normalize the work source Split the invocation into three parts: the work source, the orchestrator flags from the Parameters table, and every remaining flag, which is forge pass-through. A token that matches neither the orchestrator flag table nor forge's own flag list is **not** assumed to be a pass-through flag: stop, name the token back to the user, and suggest the near miss when there is an obvious one — `strct` almost certainly meant `strict`. Forge lives with the same open-world assumption and pays for it once; here an unrecognized token is forwarded to every dispatched run and silently dropped by each, so a user who typed `strct` gets depth triage applied across the whole batch and finds out at close-out, which is the one place the cost of a typo scales with the wave. Route the work source into one canonical task list in which each task records the route it came from, because Step 3 chooses its analysis path per task rather than per run; the five routes, their combination rules and deduplication are in **[references/entry-routes.md](references/entry-routes.md)**. If no work source resolves — a bare invocation, or flags with nothing to act on — stop and ask which issues, milestone or plan is meant; inferring a batch from the repo would dispatch work nobody asked for across several worktrees at once, which is far more expensive to undo than one question is to ask. If both `unified` and `split` are present, stop and report the conflict instead of applying a precedence, because the two ask for opposite PR topologies and silently honouring one would hide from the user which shape actually shipped. ## Step 2 — Materialize refs Every task needs a stable identifier before analysis, because the battle plan, the ledger, the worktree names and each dispatched forge run all address tasks by ref. Ref and container routes already have one; the plan and free-form routes do not, so file real issues for those tasks with **`/to-tickets`** and adopt the returned refs, or fall back to synthetic `T1..Tn` IDs when the user declines filing — a synthetic ID is local to this run and never reaches a tracker, so any later step that would write to a tracker for such a task skips that write and names the skip in the Step 9 report. The free-form route carries one exception: under `automode` its tasks are never filed, unless the user named the split themselves — for example by enumerating the tasks in the goal, or by re-invoking with refs once they have seen the recorded split — because otherwise the split is the agent's own assumption rather than a detail of a human-written request; see the free-form decomposition section of **[references/entry-routes.md](references/entry-routes.md)**. If a ref does not resolve to something its tracker can return, name it and ask whether to continue without it; under `automode`, drop it and record the omission in Step 9, because a silently missing task is indistinguishable from one that was never requested, and that is the single failure a batch run must never hide. Route mechanics — the `/to-tickets` handoff, container expansion and which trackers are supported — are in **[references/entry-routes.md](references/entry-routes.md)**. ### Scouts never interview This rule is stated here because it governs every ref Step 2 hands onward. A scout dispatched in Step 3 that hits a forge Step 4 context gap returns the open question rather than asking it, and never blocks waiting for an answer. The orchestrator batches every task's open questions into **one** consolidated interview attached to the Step 5 gate, using forge's proposed-answer format with the most likely option marked `(Recommended)` and "Other" implicit. A scout that interviewed on its own would stall a parallel fan-out behind N separate prompts and would split into N approvals the single approval the gate exists to collect. Under `automode` the `(Recommended)` answer is taken for every open question and the assumption is recorded in the battle plan, exactly as forge does at its own Step 4. ## Step 3 — Analyze Every task needs to know which files it will touch before Step 4 can build a collision graph, and there are two ways to learn that: read it off a plan someone already wrote, or pay an agent to go find out. Route **per task, not per run** — a plan covering three of five tasks is dispatch-ready for those three and scouts only the other two, because the two routes answer the same question at very different cost and a task's route says nothing about its neighbor's. | Route | When | Cost | |---|---|---| | 3a — plan-sourced | The task's route is `plan` and its slice passes the dispatch-ready check | Zero agent spawns; dispatches straight through `/forge plan <path>` | | 3b — scout fan-out | Every other task, plus any plan-sourced task that fails dispatch-ready or is `stale` | One forge Part 1 run per task | ### Step 3a — Plan-sourced A task routed to `plan` in Step 1 is analyzed by reading its own plan slice rather than by dispatching an agent to rediscover what the plan author already wrote down. The dispatch-ready check, the freshness guard that can void it, and the fallback to 3b when either fails are all in **[references/plan-sourced.md](references/plan-sourced.md)** — read it before implementing this step; its rules are not restated here. ### Step 3b — Scout fan-out Every task that is not dispatch-ready — whether it never had a plan slice or fell out of one — is analyzed by dispatching one forge Part 1 run (Steps 1 through 6, stopping at the gate) per task, run in parallel. The fan-out runs as one Workflow invocation using **`scripts/scout-fanout.mjs`**, which returns one structured proposal per task rather than prose the orchestrator would otherwise have to parse. Schema validation on that return shape is enforced at the tool layer, so a malformed scout return is retried by the runtime itself rather than parsed defensively here. Per the Overview's runtime-parallelism rule, applied here to Step 3b specifically: on a runtime without a Workflow surface, the analysis runs inline and sequentially instead, with a one-line warning naming why. ## Step 4 — Schedule Step 3's `filesToTouch`, `symbols`, `declaredBlockers`, `blastRadius` and `difficulty` feed Step 4. The first four build the collision graph in **[references/collision-graph.md](references/collision-graph.md)**: two tasks collide when their file sets intersect, one declares the other a blocker, or a plan orders one before the other, and every edge is oriented by a deterministic priority — overlap-derived edges are acyclic by construction, stated (declared or plan-order) edges are checked for cycles and dropped back to priority order when one turns up. Every task is then triaged along two independent axes — difficulty picks the model tier, blast radius picks the forge depth, both documented in [references/triage.md](references/triage.md) — before the graph's connected components are grouped into worktrees, per **[references/scheduling.md](references/scheduling.md)**: a lone task gets its own worktree, a small tightly-coupled component may run `unified` in one worktree behind one PR, and everything larger `split`s one worktree per task. Scheduling then emits waves from the oriented graph — every task with no unsatisfied blocker joins wave 1 up to the `max` concurrency ceiling, and a blocked task enters a later wave only once its blocker has relayed — and none of it dispatches anything; the whole plan surfaces at the Step 5 gate. ## Step 5 — The gate Everything Steps 1 through 4 produced is assembled into one battle plan and shown to the user. **Do not create any worktree and do not dispatch any forge run until the user approves.** This is the same invariant forge states at its own Step 6, one level up: nothing here touches the filesystem, opens a branch, or spends an agent's budget on implementation until approval is explicit. Read **[references/battle-plan.md](references/battle-plan.md)** for the literal artifact format, its required elements, and the exact approval semantics — they are not restated here. `automode` is the only sanctioned bypass, and it lifts this gate exactly as it lifts forge's Step 6: the plan is still assembled and still shown, but as a record the run already acted on rather than a question awaiting an answer. Lifting this gate does not lift any hard floor named in the Overview — no dispatched run auto-commits, auto-pushes, or writes back to a tracker under `automode` either. ## Step 6 — Provision Create one worktree per group, per the topology Step 4 decided — a lone task gets its own worktree, a `unified` component shares one worktree and one branch, a `split` component gets one worktree per task — composing `superpowers:using-git-worktrees` at every worktree exactly as forge's `worktree` flag composes it at its own Step 3, so naming, layout and cleanup match what a single forge run already does rather than inventing a second worktree convention. Write the run ledger before dispatching anything: an interrupted run is resumable only because the ledger already recorded every worktree and every task's `planned` state before Step 7 touched any of them. Read **[references/ledger.md](references/ledger.md)** for the schema, the task-state vocabulary, and the resume contract. ## Step 7 — Dispatch Dispatch one agent per task, at the tier and depth Step 4 assigned, running in that task's worktree. A plan-sourced task is dispatched as `/forge plan <slice> <pass-through-flags>`; a scouted task is dispatched as `/forge <ref> <pass-through-flags>` with the scout's proposal supplied as the already-approved plan, so the dispatched run does not re-derive what Step 3b already found. Concurrency is capped at `max` (default `4`), against the waves Step 4 already computed. Update the ledger row on every state change, so **[references/ledger.md](references/ledger.md)**'s task-state vocabulary is never stale by more than one transition. On a runtime without a parallel agent surface, dispatch sequentially in dependency order instead, with a one-line warning naming why. The same approval that supplied each run's Step 5 plan above also supplies its Step 12 commit-and-PR selection, for exactly the tasks named on the approved battle plan and no others — forge's Step 12 requires an explicit human selection before any run commits or opens a PR, and "yes, forge them" against a plan that named every task is that selection, made once for all of them. Under `automode` without `afk`, no human made it, so every dispatched run's own Step 12 stops at its plan-only output instead, exactly as a standalone `automode` forge run does; `afk` authorizes the commit-and-PR selection unattended, the same way it already authorizes the blocking-PR merge at Step 8. See **[references/afk.md](references/afk.md)** for the full three-way rule and **[references/battle-plan.md](references/battle-plan.md)** for what the plan must show before it can grant this authorization. ## Step 8 — Relay When a blocked task's blocker lands, the dependent is not released on a merged label alone — the orchestrator proves the blocker's changes are actually reachable from the dependent's base with an ancestor check, falling back to a content-and-PR-number proof when a squash or rebase merge has rewritten the SHA, then rebases the dependent onto the current base before it dispatches. The full proof sequence, the fallback, and what happens without a host PR CLI at all are in **[references/relay.md](references/relay.md)**. By default, merge authority stays with the user: the orchestrator notifies that a blocking PR is ready and parks — it never merges, on this run or any other, `automode` included. **[references/afk.md](references/afk.md)** documents the one flag that changes that, `afk`, the single sanctioned exception to forge's no-auto-push floor, and the seven-item checklist every one of its blocking-PR merges has to pass in full before it acts. ## Step 9 — Close-out The orchestrator emits one aggregate report: per task, its PR, branch, or plan-only file — whichever its own Step 12 actually produced, per the three-way commit-and-PR rule in the Overview and Step 7 — final state, depth, tier and `/goal` result; every task `budget` deferred or downgraded, named explicitly by ref and by which rung of the degradation ladder acted on it; every parked task and precisely what it is waiting on, not just that it is waiting — including, under `automode` without `afk`, any task left parked for the rest of the run because its blocker never produced anything the relay could prove ancestry against, per [references/relay.md](references/relay.md)'s "Without a landable commit" section; and a worktree cleanup reminder for every worktree Step 6 provisioned. Cleanup is **never auto-run**, inheriting forge's own `worktree` rule that losing in-progress state on inferred completion is the wrong default — a task reading `merged` in the ledger is still the orchestrator's inference from the evidence it gathered, not a claim the user has personally confirmed, and removing a worktree on that inference risks deleting something the user still wanted to look at. Two forge steps are **hoisted to the orchestrator**, because running them per task would produce N conflicting writes to the same targets: | Forge step | Dispatched run does | Orchestrator does | |---|---|---| | Step 10 — spin-off issues via `/to-tickets` | collects candidates and reports them; files nothing | dedupes across all tasks, then files once under forge's normal rules | | Step 11 — self-evolution | reports candidate lessons; writes nothing | dedupes, and proposes a single skill, rule, guide or memory edit | A worktree is exactly the boundary that keeps N parallel forge runs from touching each other's state, and both hoisted steps write outside it. Running either step inside every dispatched run would turn that shared, out-of-worktree target into an N-way race: N runs each drafting and filing their own spin-off issues for overlapping observations duplicates tracker noise nobody asked for, and N runs each proposing their own skill or memory edit competes to write the same config file from N worktrees that cannot see one another's in-flight change. The orchestrator, which alone can see every task's candidates at once, is the one place the write is safe to make. Orchestrator-level self-evolution has its own subject matter, distinct from what any single dispatched run could observe: triage misses that required a runtime promotion, edges the collision graph over- or under-serialized, and plan slices that proved stale. Those lessons belong in **[references/anti-patterns.md](references/anti-patterns.md)**, this skill's own canonical home for them, exactly as forge's is for forge's. The ledger is written to its final state before the report is emitted, so a run that is reported is always a run that can be resumed or audited — the report is read off the same file `resume` would read, never off a separate in-memory summary that could drift from it. --- ## Anti-patterns & Red Flags See **[references/anti-patterns.md](references/anti-patterns.md)** for the red-flag table — the canonical home for lessons learned during an orchestration run.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.