Skip to main content
A workflow config is a TOML file that bundles a workflow graph with its execution behavior — the goal, model, prepare steps, inputs, hooks, and other workflow-owned settings. Instead of passing a dozen CLI flags, check workflow.toml into version control and launch it with a single command:
fabro run and fabro create resolve and package the workflow locally, register its immutable workflow version and dependencies, and then ask the server to admit the intent and create a run from that version. Source parsing or packaging failures stop locally before registration; full effective-intent validation is authoritative at server admission. Use fabro preflight for explicit local validation without creating a run. fabro create stops with the run in the submitted state; fabro run performs the same create operation and then starts the run separately. The workflow can be selected by name from the current project or user workflow storage, by a path in another local checkout, or as a loose local file. Its source location does not choose the execution workspace: the directory where you invoke Fabro remains the target source. Clone-based environments derive a GitHub target from that caller directory, while a local environment receives the canonical caller directory directly. Fetching workflow definitions from a remote Git URL is not part of these commands.

Minimal example

A run config needs at minimum a schema version and a goal:
run.toml
Goal precedence: CLI --goal or --goal-file > [run].goal > Graphviz graph attribute. A CLI --goal-file is read on the invoking machine and sent as a per-run value; a goal file referenced by workflow.toml remains immutable workflow content.

Full example

run.toml

Sections

[run.model]

Override the default model and provider for all nodes that don’t have an explicit model assigned via a stylesheet.
run.toml
Provider values are catalog provider ID strings. Built-in IDs like anthropic and openai work, and settings-defined IDs like proxy work after they are added under [llm.providers.<id>]. For a qualified fallback, the selector may be that provider’s canonical model ID, alias, or API ID. Fabro splits on the first : when the part before it names a known provider, so provider API IDs may contain / or additional colons:
run.toml
This chain applies only when the original request resolves to kimi-k3. It tries direct Moonshot AI, then OpenRouter, then Claude Opus. The OpenRouter entry could equivalently be written as "openrouter:moonshotai/kimi-k3" using its API ID. Legacy provider/model fallback references remain accepted but are normalized to provider:model. A colon alone does not make a reference qualified. Many model IDs contain one — ollama name:tag values, Bedrock inference-profile IDs and ARNs — so Fabro treats the reference as qualified only when the text before the first : names a known provider. "llama3:8b" stays a single model ID, while "ollama:llama3:8b" pins the ollama provider and passes llama3:8b as the selector. At run creation, Fabro resolves the primary selector, every node selector, and the fallback table against the server’s ready-provider snapshot. It persists the selected canonical model slug and provider, so resuming the run does not choose a different provider just because credentials or priorities changed. fabro validate only checks the table’s TOML shape because it is offline and has no server model catalog. Use fabro preflight for catalog and provider checks. Each original requested model selects one fixed chain. Fabro does not jump to the chain configured for a fallback target. When a target lacks the requested reasoning level, Fabro uses the nearest supported level. It rounds equal-distance choices up. For example, a server that selects Modal as the primary kimi-k3 offering can define these independent chains:
settings.toml
If claude-fable falls back to gpt-sol, Fabro continues with claude-opus from the Fable list. It does not restart from the separate gpt-sol list. Historical built-in provider API IDs are accepted for compatibility and normalize before this selection. For example, name = "openai/gpt-5.6-sol" is treated as the canonical gpt-5.6-sol selector; omit provider to use readiness and priority, or set provider separately to pin an offering.

[run.model.controls]

Set default model controls for all nodes that do not override them in the workflow stylesheet:
run.toml

Fallback lists with splice

Use the reserved "..." marker in one model’s list to splice in that model’s inherited list from lower-precedence layers:
run.toml

[run.prepare]

Ordered list of steps to run before the workflow starts. Use this to clone repositories, install dependencies, or prepare the environment.
run.toml
Each step must exit with status 0. If any step fails, the run aborts before the workflow starts. Prepare steps replace across layers — the higher-precedence layer wins wholesale. Fabro substitutes {{ vars.* }} when the server creates the run, then resolves {{ secrets.* }} from token entries in the server vault immediately before the worker executes the steps. Secret expressions remain in the persisted run definition; resolved secret values are not persisted. A missing or non-token secret aborts startup with the affected step and token named in the error.

[run.clone]

Configure whether clone-based sandboxes clone the run’s GitHub origin before execution.
run.toml
Set enabled = false to start Docker and Daytona runs with an empty provider workspace. Use prepare steps to clone or create any files the workflow needs.

[run.run_branch]

Configure Fabro’s managed fabro/run/<id> checkpoint branch.
run.toml

[run.meta_branch]

Configure Fabro’s managed fabro/meta/<id> metadata branch.
run.toml

[run.environment] and server-managed environments

Runs select a reusable server-managed environment by slug. For fabro run and fabro create, use --environment <slug> to select it; omitting the flag selects default. Configure the catalog on the server rather than relying on the CLI machine’s settings.toml or the source checkout’s .fabro/project.toml, because those environments tables are not transmitted during intent creation.
server settings.toml
Workflow-owned sparse overrides can live under [run.environment.*] and apply to the selected server environment:
When provider = "local", Fabro runs directly in the resolved working directory. If you want local isolation, create or enter a separate clone or Git worktree yourself. Environment variable values can combine literal text with server variables and token secrets:
run.toml
Missing or non-token secret references fail closed before sandbox startup. {{ env.* }} is not supported: the process environment is not a configuration source. Use {{ vars.NAME }} for a non-sensitive value or {{ secrets.NAME }} for a credential.

[run.integrations.github.permissions]

Request a scoped GitHub App token for workflow stages that need GITHUB_TOKEN inside the sandbox. Values map directly to GitHub App permission names and access levels.
run.toml
Only requested permissions are included. The upper bound is the permission set granted to the installed GitHub App, and Fabro logs a warning and continues without GITHUB_TOKEN if the app is not configured or is not installed on the repository. This table follows the workflow settings merge rules. A higher-precedence workflow or CLI override can set permissions = {} to clear inherited permissions and run without a GitHub token.

[run.integrations.github].additional_repositories

Declare extra GitHub repositories, beyond the implicit run origin, that the minted GITHUB_TOKEN must cover. The one permissions map applies to the origin and every declared repository.
run.toml
Each entry is a full owner/repository slug. Every repository in the effective set must share one owner and be reachable by the origin repository’s GitHub App installation. A non-empty list requires contents = "read" or contents = "write". Malformed slugs, case-insensitive duplicates, cross-owner sets, and sets larger than 499 entries fail configuration validation with indexed error paths such as run.integrations.github.additional_repositories[1]. Unlike permissions-only configuration, declared additional repositories are a hard requirement: missing credentials, a missing origin, or an inaccessible declared repository fails preflight and run initialization with the repository named. The higher-precedence list replaces the lower one wholesale — no union and no ... splice — and additional_repositories = [] explicitly clears an inherited list. additional_repositories and permissions resolve independently; if layering leaves repositories declared while permissions were cleared, resolution reports the invalid combination instead of dropping either field. See Additional repositories for what works inside stages (gh, GitHub API, plain Git over HTTPS and the common SSH spellings) and for the security boundary.

[run.notifications]

Define named notification routes for run events. Slack lifecycle notifications are configured here, not in server config.
run.toml
Each enabled Slack route posts once for each matching lifecycle event. Messages include the run ID, an Open in Fabro link when available, workflow label, terminal result, duration, and pull request details when those are already present in the run event stream. run.failed is emitted only when the run terminally fails. A failed stage that routes onward to a normal completion path produces run.completed, not run.failed. If a Slack route’s channel is missing, empty, or contains an unsupported interpolation token, Fabro logs a warning and skips that route. Delivery failures are logged and never fail or alter the run.

[run.checkpoint]

Configure how git checkpoint commits behave.
run.toml
exclude_globs replaces across layers — the higher-precedence layer wins wholesale. skip_git_hooks and commit_timeout use normal override semantics: the highest layer that sets the field wins.

[run.inputs]

Define inputs that are rendered into final workflow string attributes. See Variables for the full reference.
run.toml
Inputs can be used in graph goal, root model_stylesheet, and node prompt attributes with {{ inputs.name }} syntax:
c-i.fabro
Inputs cannot parameterize workflow structure, file references such as node IDs, edges, import paths, @file paths, or child workflow paths, or any full-template attribute besides prompt, goal, and the root model_stylesheet. Command script supports only simple value substitution. Other attributes such as label are literal text. If a workflow template references an undefined input like {{ inputs.langauge }}, fabro validate reports a warning. Run-style commands promote that diagnostic to an error before creating or starting a run. TOML [run.inputs] tables replace wholesale across layers. Unlike labels, TOML input tables do not merge by key — the highest-precedence config layer that sets inputs wins its entire map. CLI input flags are sparse overrides on top of the resolved config inputs:
Repeat -I / --input to override multiple keys. CLI input flags have the highest precedence, merge per key, and preserve unrelated inherited inputs. Duplicate CLI keys are accepted; the last value wins.

[run.artifacts]

Configure automatic collection of test artifacts (Playwright reports, JUnit XML, screenshots, etc.) from the execution environment after each stage.
run.toml
Artifact collection is opt-in — when no [run.artifacts] section is present, no file scanning occurs. Artifact globs use / as the separator and have the same semantics in every sandbox:
  • * and ? match within one path segment.
  • Bracket expressions such as [abc] and [!abc] match one character.
  • ** matches across directories when used as a complete segment.
  • Leading dots are matched normally.
  • Patterns use /, are relative to the sandbox working directory, and are case-sensitive. Backslashes are invalid.
  • Absolute patterns and patterns containing a .. segment are invalid.
For example, .ai/reports/*.md matches direct Markdown children of .ai/reports, while .ai/reports/**/*.md also matches nested reports. *.trace.zip matches only the working-directory root; use **/*.trace.zip to match at any depth. To collect date-named implementation plans, use .ai/plans/????-??-??-*.md. Each collection reflects the post-stage workspace state rather than filesystem modification timestamps. A path with unchanged content is recorded only once per run; if its content changes, Fabro captures the new version. Fabro resolves the configured workspace root but does not recurse through symlinks below it while collecting artifacts. Dependency, cache, and build directories such as .git, node_modules, target, .venv, .cache, and dist are pruned. A collection is limited to 100 files, 10 MB per file, and 50 MB total.

[run.agent]

Configure workflow agent behavior that is not tied to a single stage.
run.toml
fabro_tools defaults to false. Set it to true only for runs whose agents should be able to use the same Fabro run-management MCP tool catalog exposed to human MCP clients: create, search, get, interact, gather, events, and pair. One workflow-agent exception is intentional: fabro_run_create always creates child runs parented to the current run. If an agent supplies parent_id, it must match the current run ID. This setting is independent of [run.agent.mcps], which configures external MCP servers available to the agent.

[run.agent.mcps]

Configure MCP servers available to agent stages during the workflow run. Each server is a named TOML table under [run.agent.mcps]. All three transport types are supported: stdio, http, and sandbox.
run.toml
To reuse a definition from the server-managed MCP catalog, reference its ID instead of defining an inline transport:
run.toml
Inline transport commands, URLs, env values, and headers support {{ vars.* }} and {{ secrets.* }} interpolation. As with prepare steps, server variables resolve at run creation and token secrets resolve at launch; missing values fail closed. See MCP runtime interpolation for the standalone fabro exec difference. The sandbox transport runs the MCP server inside the workflow’s sandbox. This is useful for tools that need access to the sandbox environment, such as browser automation with Playwright. See MCP for details.

[run.pull_request]

Automatically open a GitHub pull request when the workflow run completes successfully. Requires a GitHub App to be configured and a clone-based Docker or Daytona environment; run creation rejects enabled = true on a Local environment.
run.toml

[[run.hooks]]

Define hooks that run in response to lifecycle events. Each hook is a TOML array entry:
run.toml
Hook merge semantics: hooks with matching id values replace in place. Hooks without an id from a higher-precedence layer append after the fully merged inherited hook list. See Hooks for hook types beyond scripts (HTTP, prompt, agent).

Graph path resolution

The [workflow].graph path is resolved relative to the TOML file’s parent directory, not the current working directory. This means a run config and its workflow can live side by side:
Absolute paths are used as-is.

Precedence

For runs created by fabro run and fabro create, the CLI transmits sparse flags and immutable workflow content, not machine or project run defaults. Fabro resolves workflow behavior in this order (first match wins):
Stylesheet rules on individual nodes always take priority over run config values.

Project and machine settings

fabro run and fabro create do not transmit [run] or [environments] from .fabro/project.toml or the CLI machine’s ~/.fabro/settings.toml. When either key is present, the CLI warns with the affected file and key names, but never includes the values in the warning or request. Move workflow-owned behavior into each workflow.toml, and configure placement in server-managed environments. In particular, automatic pull-request behavior for CLI-created runs belongs in the workflow:
workflow.toml
There is no compatibility field in the create request and no global pull-request default supplied by the CLI. A server may still apply its own active configuration independently; the warning does not claim otherwise.

Validation

Fabro validates the run config when it loads:
  • _version check — Only _version = 1 (or missing, which defaults to 1) is accepted. The legacy top-level version key is rejected with a rename hint.
  • Unknown keys — Any top-level key not in [project], [workflow], [run], [cli], [server], or _version is rejected with a targeted rename hint pointing at the v2 replacement path.
  • Variable check — Undefined workflow or prompt template variables produce diagnostics. fabro validate reports them as warnings; run-style commands treat them as errors before creating or starting a run.
The CLI also continues to parse and validate its active machine settings and a discovered source-project config before creation. Malformed or unreadable files remain hard local failures even though their run values are not transmitted. Use fabro preflight to validate a run config without executing it: