--- title: "Fabro Language" description: "Complete reference for Fabro's Graphviz workflow language" --- Fabro workflows are written in a subset of the [Graphviz DOT language](https://graphviz.org/doc/info/lang.html) with extensions for agent orchestration. This page is the complete syntax reference. For conceptual introductions, see [Workflows](/core-concepts/workflows) and [Nodes & Stages](/workflows/stages-and-nodes). ## File structure Every workflow is a `digraph` (directed graph) with a name and a body of statements: ```dot title="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 ```dot // 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: | Type | Syntax | Examples | |---|---|---| | String | Double-quoted | `"Run tests"`, `"line1\nline2"` | | Integer | Bare digits, optional sign | `42`, `-1`, `0` | | Float | Digits with decimal point | `3.14`, `-0.5`, `.5` | | Boolean | Keywords | `true`, `false` | | Duration | Integer with unit suffix | `250ms`, `30s`, `15m`, `2h`, `1d` | | Bare string | Identifier with hyphens/dots | `claude-sonnet-4-5`, `gpt-5.2-codex` | | Identifier | Bare word | `LR`, `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: ```dot // Block syntax graph [goal="Build a feature", model_stylesheet="* { model: claude-haiku-4-5; }"] // Declaration syntax rankdir=LR ``` | Attribute | Type | Description | |---|---|---| | `goal` | String | Workflow objective — guides agent behavior and retrospectives | | `rankdir` | Identifier | Layout direction: `LR` (left-to-right) or `TB` (top-to-bottom) | | `model_stylesheet` | String | CSS-like rules for model assignment (see [Model Stylesheets](/workflows/stylesheets)) | | `default_max_retries` | Integer | Default retry count for all nodes (default: 0) | | `retry_target` | String | Default node ID to jump to on retry | | `fallback_retry_target` | String | Fallback retry target if primary target fails | | `default_fidelity` | String | Default [fidelity level](/execution/context) for all nodes | | `default_thread` | String | Default thread ID for all nodes | | `max_node_visits` | Integer | Max visits per node across the run (0 = unlimited) | | `stall_timeout` | Duration | Timeout for stalled workflows (default: `1800s`, 0 = disabled) | | `loop_restart_signature_limit` | Integer | Max times the same failure signature can repeat before aborting (default: 3) | ### Node defaults Apply default attributes to all subsequently declared nodes: ```dot 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: ```dot edge [weight=5] ``` ### Node declarations Declare a node with optional attributes: ```dot 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: ```dot 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: ```dot gate -> exit [label="Pass", condition="outcome=success"] gate -> implement [label="Fix"] ``` ### Subgraphs Group nodes visually and apply scoped defaults: ```dot 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](/workflows/stylesheets) 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](/workflows/stages-and-nodes) for detailed documentation of each type. | Shape | Handler | Purpose | |---|---|---| | `Mdiamond` | start | Workflow entry point (exactly one required) | | `Msquare` | exit | Workflow terminal (exactly one required) | | `box` (default) | agent | Multi-turn LLM with tool access | | `tab` | prompt | Single LLM call, no tools | | `parallelogram` | command | Execute a shell script | | `hexagon` | human | Human-in-the-loop decision gate | | `diamond` | conditional | Route based on conditions | | `component` | parallel | Fan-out to concurrent branches | | `tripleoctagon` | parallel.fan_in | Merge parallel branch results | | `insulator` | wait | Pause for a duration | | `house` | stack.manager_loop | Sub-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 | Attribute | Type | Description | |---|---|---| | `label` | String | Display name in the graph visualization | | `shape` | Identifier | Graphviz shape — determines handler type (see table above) | | `type` | String | Explicit handler type (overrides shape) | | `class` | String | Comma-separated classes for [stylesheet](/workflows/stylesheets) targeting | | `timeout` | Duration | Execution timeout (e.g. `900s`) | | `max_visits` | Integer | Max times this node can execute in a run. Overrides the graph-level `max_node_visits` for this node. | | `max_retries` | Integer | Override default retry count | | `retry_policy` | String | Named preset: `none`, `standard`, `aggressive`, `linear`, `patient` | | `retry_target` | String | Node ID to jump to on retry | | `fallback_retry_target` | String | Fallback node ID if primary `retry_target` is unreachable | | `goal_gate` | Boolean | When `true`, workflow fails if this node didn't finish with `success` or `partial_success`. See [Node Outcomes](/execution/outcomes#goal-gate-interaction). | | `auto_status` | Boolean | When `true`, overrides any non-`success`/non-`skipped` status to `success` after the handler completes. See [Node Outcomes](/execution/outcomes#auto_status). | | `allow_partial` | Boolean | When `true` and retries are exhausted on a `retry` status, promotes the outcome to `partial_success` instead of `fail`. Default `false`. See [Node Outcomes](/execution/outcomes#allow_partial). | | `selection` | String | Edge tiebreaking strategy: `deterministic` (default) or `random` (weighted-random). Cannot be combined with conditional edges. | ### Agent and prompt nodes | Attribute | Type | Description | |---|---|---| | `prompt` | String | Task instructions for the LLM. Supports file references with `@path/to/file.md` | | `reasoning_effort` | String | `low`, `medium`, or `high` (default: `high`) | | `max_tokens` | Integer | Maximum output tokens | | `fidelity` | String | How much prior context is passed: `compact`, `full`, `summary:high`, `summary:medium`, `summary:low`, `truncate` | | `thread_id` | String | Groups nodes into a shared conversation thread | | `model` | String | Explicit model ID (overrides stylesheet) | | `provider` | String | Explicit provider name (overrides stylesheet). Auto-inferred from the model catalog when omitted. | | `project_memory` | Boolean | When `true` (default), prompt nodes discover and include project docs (`AGENTS.md`, `CLAUDE.md`, etc.) as a system prompt. Set to `false` to disable. | | `backend` | String | Agent 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](/core-concepts/agents#backends). | ### Command nodes | Attribute | Type | Description | |---|---|---| | `script` | String | Shell command to execute | | `language` | String | `"shell"` (default) or `"python"` | ### Parallel (fan-out) nodes | Attribute | Type | Description | |---|---|---| | `join_policy` | String | When the merge can proceed: `wait_all` (default), `first_success` | | `max_parallel` | Integer | Maximum concurrent branches (default: 4) | ### Wait nodes | Attribute | Type | Description | |---|---|---| | `duration` | Duration | How long to pause (required). E.g. `"30s"`, `"2m"` | ### Manager loop (sub-workflow) nodes | Attribute | Type | Description | |---|---|---| | `stack.child_workflow` | String | Path to child workflow `.fabro` file (required unless inline source given) | | `stack.child_dot_source` | String | Inline DOT source for the child workflow (alternative to file path) | | `manager.poll_interval` | Duration | How often the manager checks the child workflow (default: `45s`) | | `manager.max_cycles` | Integer | Maximum polling cycles before timeout (default: 1000) | | `manager.stop_condition` | String | Condition expression — when true, the child run is stopped early | ## Edge attributes | Attribute | Type | Description | |---|---|---| | `label` | String | Display text; also used for human gate option matching | | `condition` | String | Boolean expression for conditional routing (see below) | | `weight` | Integer | Priority for tiebreaking (higher wins, default: 0) | | `fidelity` | String | Override fidelity level for this transition | | `thread_id` | String | Override thread ID for this transition | | `loop_restart` | Boolean | Mark this edge as a loop restart point | | `freeform` | Boolean | When `true` on a human-gate edge, accept free-text input instead of fixed choices | ## Condition expressions Edge conditions are boolean expressions evaluated against the stage outcome and run context. See [Transitions](/workflows/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) Value ::= BareWord | '"' QuotedString '"' Op ::= '=' | '!=' | '>' | '<' | '>=' | '<=' | 'contains' | 'matches' ``` ### Keys | Key | Resolves to | |---|---| | `outcome` | Stage status: `success`, `fail`, `partial_success`, or `skipped`. See [Node Outcomes](/execution/outcomes#outcome-in-edge-conditions). | | `preferred_label` | Label selected by a human gate or LLM routing directive | | `context.KEY` | Value from the run context | | `KEY` | Shorthand for context lookup (without the `context.` prefix) | ### Operators | Operator | Example | Description | |---|---|---| | `=` | `outcome=success` | Equality | | `!=` | `outcome!=fail` | Inequality | | `>` | `context.score > 80` | Greater than (numeric) | | `<` | `context.count < 5` | Less than (numeric) | | `>=` | `context.score >= 80` | Greater than or equal | | `<=` | `context.count <= 10` | Less than or equal | | `contains` | `context.message contains error` | Substring match or array membership | | `matches` | `context.version matches ^v\d+` | Regular expression match | | `&&` | `a=1 && b=2` | Logical AND (binds tighter than `\|\|`) | | `\|\|` | `a=1 \|\| b=2` | Logical OR | | `!` | `!outcome=fail` | Logical 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 ```dot // 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: ```dot 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): ```dot shared [prompt="@~/shared-prompts/review.md"] parent [prompt="@../common/plan.md"] ``` Untracked `@file` references (files not committed to git) are inlined into the Graphviz 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 - `thread_id` requires `fidelity="full"` (session reuse depends on full fidelity) - Known handler types only ## Complete example ```dot title="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 } ```