Concept 9 of 10
Three repos, three slightly different PR policies, so you copy-pasted create_pr and now you maintain three of them — and you're paying frontier prices for steps a 7B model could do. Write the building block once, compose it anywhere, layer per-project rules on top without forking, and pick the model each step actually needs.
A capability is a reusable building block — a small
sub-workflow that does one thing (your create_pr, your
verify-tests) behind a typed input/output contract. It keeps
its own shared state, the scratchpad each step reads from and writes to as
the workflow runs. A larger workflow that wires several capabilities
together is the orchestrator; it hands each one its inputs
and collects its outputs. Fix a bug in create_pr once and every
orchestrator that uses it inherits the fix — no more hunting down three
forked copies. Break its contract and every dependent fails at load time,
not in production.
workflows:
cap.verify.workspace-green: # a capability: one job, reusable
snippet: # the snippet block IS its typed contract
inputs: { repo: { type: string, required: true } }
outputs: { green: { type: boolean } }
# ... its own state machine, with its own shared state
flow.add-feature: # an orchestrator: composes capabilities
inputs:
repo: { type: string, required: true }
states:
verifying:
transitions:
verify:
target: done
executor:
kind: workflow
definitionId: cap.verify.workspace-green
use: # $.context.* = the orchestrator's shared state
inputs: { repo: "$.context.repo" } # read from shared state
outputs: { "$.context.workspace_green": green }# write the result back
The reason you forked create_pr in the first place was a
per-repo rule — this project squash-merges, that one needs two reviewers.
Instead of three copies, the rules layer: your personal defaults, your
project's rules, your team's rules — stacked, so a project tweak overrides
one rule without forking the whole building block. The shared
create_pr stays one definition; each layer only declares what
it changes. Fix the base once and every project still inherits it.
The model is chosen per step. A trivial classification can run on a small
local model; a hard refactor can escalate to a frontier model; a
deterministic step needs no model at all. You stop paying frontier prices
for work that doesn't need frontier reasoning. You can name a model
directly, or tag the step with an affinity: and let
models.yaml resolve which model fills it — so swapping models is
one file, not a find-and-replace across workflows.
Picking a model per step is a run-mode capability: it works when flowgate drives the loop, not when it sits behind Claude Code or Cursor as a gateway. How to run it lays out which mode does what.
triaging:
transitions:
triage:
target: triaged
executor:
kind: llm
model: anthropic:claude-sonnet-4-6 # cheapest sufficient model for this step
prompt_template: |
Classify the issue in $.context.issue_body. Pick one transition.
max_iterations: 3