Groundwork for the dynamic-workflow migration. Blocks every harness issue — nothing should ship a .js before this convention exists.
Source: docs/plans/dynamic-workflow-migration.md §Repo-level consequences, items 1–3 (merged in #2162).
Why
The migration establishes a third authoring substrate — prose skill / shell script / workflow .js. The repo has rules for the first two only. Without a written gate, the next author reaches for a harness on any skill that merely looks parallel.
1. .claude/rules/workflow-vs-skill.md
Scope it with paths: per .claude/rules/context-engineering.md so it is not always-loaded:
paths: ["*/skills/**/workflows/*.js", "*/skills/**/SKILL.md"]
Content:
- The three gating axes. Is N enumerable by something other than the model? Is the barrier load-bearing? Is the loop bound script-decidable?
- The separating criterion, which is the plan's central finding: the fan-out width must already be enumerable by something other than the model. Every mover reads its N from a source that already exists (
list-components.sh's COMPONENT= rows, a candidate manifest, len(evals) × runs × configs); every rejection either invented its N or duplicated a bound a script already enforces.
- The cost rule that killed 6 of 18 candidates: if the fan-out width or the classification is already computed by a
check-*.sh, a workflow that re-derives it in agents is offload-to-deterministic-substrate.md run backwards.
- The two shapes with no fit here, and why — tournament (this repo's ranking signals are deterministic scores from
grade_deterministic.py, not judgements) and loop-until-done (every loop here is bounded by an enumerable set before it starts; the one true loop, workflow-checkpoint-refactor, needs state across session boundaries and a workflow script runs inside one invocation).
- The
context: fork corollary: if a skill already holds fork, "the output floods the main window" is not available as a justification. That argument is already spent for 8 skills.
Position it as a sibling of loop-integrity.md and structured-script-output.md, directly under offload-to-deterministic-substrate.md.
2. CLAUDE.md
- One row in the Rules table for the new rule.
- One line in Project Structure adding
workflows/ to the plugin layout: skills/<skill-name>/workflows/*.js.
Nothing else — the file is hand-curated and this is a directory-layout fact, not a new lifecycle.
3. .claude/rules/plugin-structure.md
- Location convention: bundled beside
SKILL.md, named <purpose>.workflow.js.
- A bundled workflow must be reachable from a
## Workflow harness (template) section in its sibling SKILL.md. An orphan .js is dead weight.
4. The reusable framing snippet
The blog post's guidance is that a skill-bundled workflow be framed as a template to adapt, not a script to run verbatim. Land this snippet in the rule so every harness issue can copy it verbatim — slot 3 is what makes it a template, because naming what an adapter may rewrite implies everything else is structure:
## Workflow harness (template)
`workflows/<name>.workflow.js` ships beside this skill. **It is a TEMPLATE to adapt,
not a script to run verbatim.** Read it, then rewrite it for the work in front of you.
**Adapt freely:** [the agent prompts, the partition/fan-out width, the filter rules,
the project-specific commands].
**Preserve across any adaptation:** [(a) the loop bound comes from <the deterministic
source>, never from a prose "for each"; (b) <the schema/enum that forces a determinate
verdict>; (c) <the barrier and why it is a barrier>].
**Skip the harness when:** [<the modal small case>] — that is a linear pass and the
harness is pure overhead. The steps below remain the authoritative description of
*what* each stage must produce; the harness only fixes *how* the work is split.
Plus two clauses every template dispatching isolation:'worktree' agents must carry:
Never Workflow({resumeFromRunId}) to retry a few failed worktree agents — a resume re-runs agents that already succeeded and opens duplicate PRs (#1868). Re-dispatch the failed units fresh and sequentially after checking gh pr list --head <branch> --state all --json number,state.
Push, PR creation, and GitHub mutations happen only in the single sequential finalise stage, never inside a fanned-out agent.
Explicitly out of scope
No new skill in workflow-orchestration-plugin. The convention is about a page and belongs in the paths:-scoped rule — a skill would cost listing tokens in every session for content read once per authoring act.
Acceptance
Groundwork for the dynamic-workflow migration. Blocks every harness issue — nothing should ship a
.jsbefore this convention exists.Source:
docs/plans/dynamic-workflow-migration.md§Repo-level consequences, items 1–3 (merged in #2162).Why
The migration establishes a third authoring substrate — prose skill / shell script / workflow
.js. The repo has rules for the first two only. Without a written gate, the next author reaches for a harness on any skill that merely looks parallel.1.
.claude/rules/workflow-vs-skill.mdScope it with
paths:per.claude/rules/context-engineering.mdso it is not always-loaded:Content:
list-components.sh'sCOMPONENT=rows, a candidate manifest,len(evals) × runs × configs); every rejection either invented its N or duplicated a bound a script already enforces.check-*.sh, a workflow that re-derives it in agents isoffload-to-deterministic-substrate.mdrun backwards.grade_deterministic.py, not judgements) and loop-until-done (every loop here is bounded by an enumerable set before it starts; the one true loop,workflow-checkpoint-refactor, needs state across session boundaries and a workflow script runs inside one invocation).context: forkcorollary: if a skill already holdsfork, "the output floods the main window" is not available as a justification. That argument is already spent for 8 skills.Position it as a sibling of
loop-integrity.mdandstructured-script-output.md, directly underoffload-to-deterministic-substrate.md.2.
CLAUDE.mdworkflows/to the plugin layout:skills/<skill-name>/workflows/*.js.Nothing else — the file is hand-curated and this is a directory-layout fact, not a new lifecycle.
3.
.claude/rules/plugin-structure.mdSKILL.md, named<purpose>.workflow.js.## Workflow harness (template)section in its siblingSKILL.md. An orphan.jsis dead weight.4. The reusable framing snippet
The blog post's guidance is that a skill-bundled workflow be framed as a template to adapt, not a script to run verbatim. Land this snippet in the rule so every harness issue can copy it verbatim — slot 3 is what makes it a template, because naming what an adapter may rewrite implies everything else is structure:
Plus two clauses every template dispatching
isolation:'worktree'agents must carry:Explicitly out of scope
No new skill in
workflow-orchestration-plugin. The convention is about a page and belongs in thepaths:-scoped rule — a skill would cost listing tokens in every session for content read once per authoring act.Acceptance
.claude/rules/workflow-vs-skill.mdexists,paths:-scoped, covering all five content bulletsCLAUDE.mdrules-table row + Project Structure lineplugin-structure.mdlocation + reachability convention