File structure
Every workflow is adigraph (directed graph) with a name and a body of statements:
my-workflow.fabro
digraph is supported — graph (undirected) and strict are not. The graph name is required. Semicolons after statements are optional.
Comments
Value types
Attribute values in[key=value] blocks can be:
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:Node defaults
Apply default attributes to all subsequently declared nodes:Edge defaults
Apply default attributes to all subsequently declared edges:Node declarations
Declare a node with optional attributes:run_tests, gate_1, _private).
Nodes referenced in edges are auto-created if not explicitly declared.
Edge declarations
Connect nodes with directed edges:A -> B -> C expand to individual edges A -> B and B -> C, all sharing the same attributes.
Edges can have attributes:
Subgraphs
Group nodes visually and apply scoped defaults: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’sshape attribute determines its execution behavior. See Nodes & Stages for detailed documentation of each type.
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
Agent and prompt nodes
Structured output validation
output_schema opts an agent, prompt, or command node into strict JSON validation:
output_schema="routing"requires a JSON object with at least one recognized routing field:preferred_next_label,outcome,failure_reason,suggested_next_ids, orcontext_updates.output_schema="@schemas/audit-result.schema.json"loads a JSON Schema file using workflow file-reference rules and validates the final JSON object in the response text. Inline JSON Schema object strings are also accepted, but file references are usually easier to read.- On validation failure, Fabro sends validation feedback to the same active context before failing: prompt nodes keep the prior assistant response in the message list, and API-backed agent nodes repair in the same live session.
output_retriesdefaults to2and controls only these corrective structured-output turns. Negative values are treated as0. It is not the same asmax_retriesand does not consume workflow retry attempts.- Custom schema output is stored in context at
output.{node_id}. Routing schema output updates routing fields and anycontext_updates. - Agent routing fallbacks still apply to
output_schema="routing": response text first, thenstatus.json, then the last file touched by the agent. The last-file fallback only accepts.jsonand.mdfiles (case-insensitive) whose final JSON object contains the routing directive; only whitespace may follow it. Custom schemas and prompt nodes validate response text only. - Command nodes validate merged stdout and stderr only after the script exits with code
0, using the same object selection as agents and prompts: custom schemas validate the last JSON object, androutingvalidates the last JSON object containing a recognized routing field. Print the intended JSON object last. A validation error is a deterministic, non-retryable failure with no repair turn orstatus.jsonfallback.output_retries,retry_policy, andmax_retriesdo not retry it. Nonzero exits retain normal command failure behavior without schema validation. - For commands, custom schema output is stored at
output.{node_id}; edge conditions cannot traverse into its fields. Theroutingschema applies routing fields and mergescontext_updatesinto flat context keys, which conditions can read (for example,context.kept_count). backend="acp"withoutput_schemais unsupported in this release.
Command nodes
Parallel (fan-out) nodes
For the first node in each branch,
fidelity resolves from the fork-to-branch edge, then the branch node; without either, the fork preamble is inherited unchanged. Branch-specific preambles are rendered before fan-out from the fork’s context snapshot. Concurrent branches cannot share sessions, so explicit branch full becomes summary:high, and branch-level thread_id is inert.
Wait nodes
Human nodes
Manager loop (sub-workflow) nodes
Edge attributes
Condition expressions
Edge conditions are boolean expressions evaluated against the stage outcome and run context. See Transitions for the full routing logic.Grammar
Keys
Operators
A bare key with no operator is a truthiness check — it passes if the value is non-empty, not
"false", and not "0".
Examples
Prompt and schema file references
Instead of inlining long prompts or JSON Schemas, reference an external file:@ prefix tells Fabro to load the referenced file relative to the workflow file. Paths support ~ (home directory) and .. (parent directory):
@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
promptattribute @filereferences point to existing files- Conditional nodes have multiple outgoing edges with conditions
- Retry targets reference existing nodes
- Goal gates have retry configuration
thread_idrequiresfidelity="full"(session reuse depends on full fidelity)- Known handler types only
Complete example
implement-feature.fabro