Skip to main content
Fabro uses {{ ... }} templates for workflow strings and prompts.

Template context

Workflow and prompt templates can reference:
ExpressionResolves to
{{ goal }}The workflow goal
{{ inputs.name }}A value from [run.inputs]
Environment variables are not available in workflow or prompt templates. Use {{ env.NAME }} only in config strings and HTTP hook headers.

Run config inputs

Define typed inputs in [run.inputs]:
run.toml
_version = 1

[workflow]
graph = "check.fabro"

[run]
goal = "Run repository checks"

[run.inputs]
repo_name = "fabro"
repo_url = "https://github.com/fabro-sh/fabro"
language = "rust"
These values are available throughout the workflow as {{ inputs.* }}:
check.fabro
digraph Check {
    graph [goal="Run tests for {{ inputs.repo_name }}"]

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

    clone [label="Clone", shape=parallelogram, script="git clone {{ inputs.repo_url }} repo"]
    test  [label="Test", prompt="Run the {{ inputs.language }} test suite in the repo/ directory."]

    start -> clone -> test -> exit
}

goal

Agent and prompt nodes also receive the workflow goal at runtime:
example.fabro
digraph Example {
    graph [goal="Implement the login feature"]

    plan [label="Plan", prompt="Create a plan for: {{ goal }}"]
}
That prompt becomes Create a plan for: Implement the login feature.

Expansion timing

Fabro expands templates in multiple passes:
  1. Before DOT parsing, {{ inputs.* }} can parameterize structural parts of the graph, including imported .fabro files.
  2. After parsing, all string graph, node, and edge attributes are rendered again with the real { goal, inputs } context.
  3. Agent and prompt handlers do a final runtime render pass as a safety net.
{{ goal }} is preserved through the pre-parse step so it can be resolved later. That means goal-dependent MiniJinja control flow such as {% if goal %} is not useful in structural pre-parse templates.

Undefined variables

Fabro uses strict undefined-variable handling. If a workflow template references an unknown value such as {{ inputs.langauge }}, validation fails instead of passing the literal text through to the model.

Escaping

To emit literal template syntax, use MiniJinja escaping:
test [prompt="{% raw %}{{ goal }}{% endraw %}"]
You can also emit literal braces with expressions such as {{ '{{' }} when needed.

Input merging

[run.inputs] intentionally replaces the inherited map wholesale rather than merging by key. Whichever layer has the highest precedence and sets [run.inputs] wins its entire map.
SourcePriority
CLI flags (-V key=value, repeated)Highest
workflow.toml [run.inputs]
.fabro/project.toml [run.inputs]
~/.fabro/settings.toml [run.inputs]Lowest
If you need per-key overrides on top of inherited defaults, set each input explicitly in the winning layer.