Defining a stylesheet
Stylesheets are set in themodel_stylesheet graph attribute:
example.fabro
- spec gets Haiku (matches
*) - implement and test get Sonnet with high reasoning (match
.coding) - review gets Gemini Pro (matches
#review)
Template stylesheets
The root graph’smodel_stylesheet is a MiniJinja template. It can read typed run inputs and server-managed variables through inputs and vars:
variable-effort.fabro
{% set %}, and normal local values such as loop. They do not expose goal, env, or secrets.
Fabro renders a stylesheet once. If an input or variable contains {{ ... }} or {% ... %}, that text stays literal. Fabro does not render it again.
Template output is not escaped as stylesheet syntax. Map user-facing choices to fixed declarations instead of inserting unrestricted text directly:
model_stylesheet does not support the @file shorthand.
Fabro uses this order:
- Parse the DOT source.
- Expand workflow imports and supported file references.
- Render the root
model_stylesheetwith{ inputs, vars }. - Parse and apply the rendered stylesheet.
- Resolve model and provider selectors.
- Validate the transformed graph.
model_stylesheet on an imported graph is ignored and produces an imported_model_stylesheet_ignored warning. Put the stylesheet on the root graph. A root stylesheet can target imported nodes by their generated IDs, classes, or shapes.
If an input or variable is unavailable, fabro validate reports template_undefined_variable. It skips stylesheet syntax and model checks for that validation pass. Run-style commands treat the same diagnostic as an error before they create or start a run.
Selectors
Each rule starts with a selector that determines which nodes it applies to:Assigning classes
Set theclass attribute on a node to target it with class selectors. Separate multiple classes with spaces:
.coding and .critical rules.
Properties
Stylesheets support five properties:
See Models for the full list of model IDs and aliases.
Specificity and cascading
When multiple rules match the same node, the rule with the highest specificity wins. This follows the same principle as CSS:id="review" and class="coding" gets gpt-5.4 because #id (specificity 3) beats .class (specificity 2).
If two rules have the same specificity, the last one in the stylesheet wins.
Explicit attributes override stylesheets
A model set directly on a node attribute always takes precedence over stylesheets, regardless of specificity:.coding sets model: claude-sonnet-4-5, this node uses Opus because the explicit attribute wins.
Syntax reference
The stylesheet syntax is a simplified subset of CSS:- Selectors:
*,shape,.class,#id - Properties and values are separated by
: - Declarations are separated by
; - Whitespace is flexible — newlines and indentation are ignored
- CSS block comments (
/* ... */) can appear between tokens and are ignored - Comments close at the first
*/and do not nest; an unterminated comment is a syntax error //starts a comment in the surrounding workflow file, but not inside a stylesheet
Full example
- Defaults everything to Haiku with low reasoning
- Overrides all agent nodes (
boxshape) to high reasoning - Keeps prompt nodes (
tabshape) at low reasoning - Routes
.codingnodes to Sonnet - Routes
.reviewnodes to Gemini for independent critique - Routes the
final_checknode to Opus for maximum quality