Skip to main content
Fabro workflows are written in a subset of the Graphviz DOT language with extensions for agent orchestration. This page is the complete syntax reference. For conceptual introductions, see Workflows and Nodes & Stages.

File structure

Every workflow is a digraph (directed graph) with a name and a body of statements:
my-workflow.fabro
digraph MyWorkflow {
    graph [goal="Describe the project"]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    scan    [label="Scan Files", shape=parallelogram, script="find . -type f | head -30"]
    analyze [label="Analyze", shape=tab, prompt="Summarize the project structure."]

    start -> scan -> analyze -> exit
}
Only digraph is supported — graph (undirected) and strict are not. The graph name is required. Semicolons after statements are optional.

Comments

// Line comment — everything to end of line

/* Block comment
   spanning multiple lines */
Comments inside quoted strings are preserved as literal text.

Value types

Attribute values in [key=value] blocks can be:
TypeSyntaxExamples
StringDouble-quoted"Run tests", "line1\nline2"
IntegerBare digits, optional sign42, -1, 0
FloatDigits with decimal point3.14, -0.5, .5
BooleanKeywordstrue, false
DurationInteger with unit suffix250ms, 30s, 15m, 2h, 1d
Bare stringIdentifier with hyphens/dotsclaude-sonnet-4-5, gpt-5.2-codex
IdentifierBare wordLR, box, Mdiamond
Escape sequences in quoted strings: \", \\, \n, \t. Duration units: ms (milliseconds), s (seconds), m (minutes), h (hours), d (days).

Statements

The body of a digraph can contain these statement types:

Graph attributes

Set workflow-level configuration:
// Block syntax
graph [goal="Build a feature", model_stylesheet="* { model: claude-haiku-4-5; }"]

// Declaration syntax
rankdir=LR
AttributeTypeDescription
goalStringWorkflow objective — guides agent behavior and retrospectives
rankdirIdentifierLayout direction: LR (left-to-right) or TB (top-to-bottom)
model_stylesheetStringCSS-like rules for model assignment (see Model Stylesheets)
default_max_retryIntegerDefault retry count for all nodes (default: 3)
retry_targetStringDefault node ID to jump to on retry
fallback_retry_targetStringFallback retry target if primary target fails
default_fidelityStringDefault fidelity level for all nodes
default_threadStringDefault thread ID for all nodes
max_node_visitsIntegerMax visits per node across the run (0 = unlimited)
stall_timeoutDurationTimeout for stalled workflows (default: 1800s, 0 = disabled)

Node defaults

Apply default attributes to all subsequently declared nodes:
node [shape=box, timeout="900s"]
Defaults are scoped to their enclosing subgraph. Explicit attributes on individual nodes override defaults.

Edge defaults

Apply default attributes to all subsequently declared edges:
edge [weight=5]

Node declarations

Declare a node with optional attributes:
plan [label="Plan", prompt="Create an implementation plan."]
Node identifiers must start with a letter or underscore, followed by letters, digits, or underscores (e.g. run_tests, gate_1, _private). Nodes referenced in edges are auto-created if not explicitly declared.

Edge declarations

Connect nodes with directed edges:
start -> plan -> implement -> exit
Chained edges like A -> B -> C expand to individual edges A -> B and B -> C, all sharing the same attributes. Edges can have attributes:
gate -> exit      [label="Pass", condition="outcome=success"]
gate -> implement [label="Fix"]

Subgraphs

Group nodes visually and apply scoped defaults:
subgraph cluster_impl {
    label = "Implementation"
    node [thread_id="impl", fidelity="full"]

    plan      [label="Plan"]
    implement [label="Implement"]
    review    [label="Review"]
}
When a subgraph has a label, it is converted to a CSS class name and applied to all nodes within the subgraph (e.g. "Implementation" becomes class implementation, "Loop A" becomes loop-a). This enables stylesheet targeting. Node and edge defaults declared inside a subgraph are scoped — they don’t leak to the outer graph. Edges can cross subgraph boundaries.

Node types

Each node’s shape attribute determines its execution behavior. See Nodes & Stages for detailed documentation of each type.
ShapeHandlerPurpose
MdiamondstartWorkflow entry point (exactly one required)
MsquareexitWorkflow terminal (exactly one required)
box (default)agentMulti-turn LLM with tool access
tabpromptSingle LLM call, no tools
parallelogramcommandExecute a shell script
hexagonhumanHuman-in-the-loop decision gate
diamondconditionalRoute based on conditions
componentparallelFan-out to concurrent branches
tripleoctagonparallel.fan_inMerge parallel branch results
insulatorwaitPause for a duration
housestack.manager_loopSub-workflow orchestration
The type attribute can also be set explicitly to override the shape-based mapping. Start nodes can also be identified by ID (start or Start). Exit nodes can be identified by ID (exit, Exit, end, or End).

Node attributes

All nodes

AttributeTypeDescription
labelStringDisplay name in the graph visualization
shapeIdentifierGraphviz shape — determines handler type (see table above)
typeStringExplicit handler type (overrides shape)
classStringComma-separated classes for stylesheet targeting
timeoutDurationExecution timeout (e.g. 900s)
max_visitsIntegerMax times this node can execute in a run. Overrides the graph-level max_node_visits for this node.
max_retriesIntegerOverride default retry count
retry_policyStringNamed preset: none, standard, aggressive, linear, patient
retry_targetStringNode ID to jump to on retry
goal_gateBooleanWhen true, workflow fails if this node doesn’t succeed
auto_statusBooleanAuto-generate status updates

Agent and prompt nodes

AttributeTypeDescription
promptStringTask instructions for the LLM. Supports file references with @path/to/file.md
reasoning_effortStringlow, medium, or high (default: high)
max_tokensIntegerMaximum output tokens
fidelityStringHow much prior context is passed: compact, full, summary:high, summary:medium, summary:low, truncate
thread_idStringGroups nodes into a shared conversation thread
modelStringExplicit model ID (overrides stylesheet)
providerStringExplicit provider name (overrides stylesheet). Auto-inferred from the model catalog when omitted.
project_memoryBooleanWhen true (default), prompt nodes discover and include project docs (AGENTS.md, CLAUDE.md, etc.) as a system prompt. Set to false to disable.
backendStringAgent execution backend. api (default): Fabro calls the LLM API directly and runs its own tool loop. cli: Fabro delegates to an external CLI tool (claude, codex, or gemini based on provider). See Agents — Backends.

Command nodes

AttributeTypeDescription
scriptStringShell command to execute
languageString"shell" (default) or "python"

Parallel (fan-out) nodes

AttributeTypeDescription
join_policyStringWhen the merge can proceed: wait_all (default), first_success, k_of_n(N), quorum(F)
error_policyStringHow branch failures are handled: continue (default), fail_fast, ignore
max_parallelIntegerMaximum concurrent branches (default: 4)

Wait nodes

AttributeTypeDescription
durationDurationHow long to pause (required). E.g. "30s", "2m"

Edge attributes

AttributeTypeDescription
labelStringDisplay text; also used for human gate option matching
conditionStringBoolean expression for conditional routing (see below)
weightIntegerPriority for tiebreaking (higher wins, default: 0)
fidelityStringOverride fidelity level for this transition
thread_idStringOverride thread ID for this transition
loop_restartBooleanMark this edge as a loop restart point

Condition expressions

Edge conditions are boolean expressions evaluated against the stage outcome and run context. See Transitions for the full routing logic.

Grammar

Expr       ::= OrExpr
OrExpr     ::= AndExpr ('||' AndExpr)*
AndExpr    ::= UnaryExpr ('&&' UnaryExpr)*
UnaryExpr  ::= '!' UnaryExpr | Clause
Clause     ::= Key Op Value | Key        (bare key = truthy check)
Op         ::= '=' | '!=' | '>' | '<' | '>=' | '<='
             | 'contains' | 'matches'

Keys

KeyResolves to
outcomeStage status: success, fail, or partial_success
preferred_labelLabel selected by a human gate or LLM routing directive
context.KEYValue from the run context
KEYShorthand for context lookup (without the context. prefix)

Operators

OperatorExampleDescription
=outcome=successEquality
!=outcome!=failInequality
>context.score > 80Greater than (numeric)
<context.count < 5Less than (numeric)
>=context.score >= 80Greater than or equal
<=context.count <= 10Less than or equal
containscontext.message contains errorSubstring match or array membership
matchescontext.version matches ^v\d+Regular expression match
&&a=1 && b=2Logical AND (binds tighter than ||)
||a=1 || b=2Logical OR
!!outcome=failLogical NOT
A bare key with no operator is a truthiness check — it passes if the value is non-empty, not "false", and not "0".

Examples

// Simple outcome check
gate -> exit [condition="outcome=success"]

// Compound condition
gate -> deploy [condition="outcome=success && context.tests_passed=true"]

// Either outcome
gate -> proceed [condition="outcome=success || outcome=partial_success"]

// Negation
gate -> retry [condition="!outcome=success"]

// Numeric comparison
gate -> fast_path [condition="context.score > 80"]

// Substring search
gate -> alert [condition="context.log contains error"]

// Regex match
gate -> v2 [condition="context.version matches ^v2\\."]

// Unconditional fallback (no condition attribute)
gate -> slow_path

Prompt file references

Instead of inlining long prompts, reference an external file:
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
The @ prefix tells Fabro to load the prompt from a file path relative to the workflow file. Paths support ~ (home directory) and .. (parent directory):
shared [prompt="@~/shared-prompts/review.md"]
parent [prompt="@../common/plan.md"]
Untracked @file references (files not committed to git) are inlined into the DOT source at prepare time, so they work even inside sandboxes that only see the git tree. Fabro validates @file references at parse time — if the referenced file does not exist, validation fails with a clear error pointing to the bad reference.

Validation

Fabro validates workflows at parse time and reports diagnostics. Key rules:
  • Exactly one start node and one exit node
  • All nodes reachable from start
  • No incoming edges to start, no outgoing edges from exit
  • Edge targets reference existing nodes
  • Condition expressions parse correctly
  • Stylesheet syntax is valid
  • LLM nodes (agent, prompt) have a prompt attribute
  • @file references point to existing files
  • Conditional nodes have multiple outgoing edges with conditions
  • Retry targets reference existing nodes
  • Goal gates have retry configuration
  • Known handler types only

Complete example

implement-feature.fabro
digraph ImplementFeature {
    graph [
        goal="Implement a feature with tests and code review",
        model_stylesheet="
            *        { model: claude-haiku-4-5;reasoning_effort: low; }
            .coding  { model: claude-sonnet-4-5;reasoning_effort: high; }
            #review  { model: claude-sonnet-4-5;reasoning_effort: high; }
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    // Planning phase
    plan [label="Plan", shape=tab, prompt="Create a detailed implementation plan for: $goal"]

    // Human approval
    approve [shape=hexagon, label="Approve Plan"]

    // Implementation (threaded for context continuity)
    subgraph cluster_impl {
        label = "Implementation"
        node [thread_id="impl", fidelity="full"]
        implement [label="Implement", class="coding", prompt="Implement the approved plan."]
        test      [label="Write Tests", class="coding", prompt="Write comprehensive tests."]
    }

    // Validation
    validate [label="Run Tests", shape=parallelogram, script="cargo test 2>&1 || true"]
    gate     [shape=diamond, label="Tests passing?"]

    // Review
    review [label="Code Review", shape=tab, prompt="Review the implementation for correctness."]

    // Wiring
    start -> plan -> approve

    approve -> implement [label="[A] Approve"]
    approve -> plan      [label="[R] Revise"]

    implement -> test -> validate -> gate

    gate -> review    [label="Pass", condition="outcome=success"]
    gate -> implement [label="Fix"]

    review -> exit
}