Skip to main content
A run config is a TOML file that bundles a workflow graph with all the settings needed to execute it — the goal, model, sandbox, prepare steps, inputs, and hooks. Instead of passing a dozen CLI flags, you check a .toml file into version control and launch with a single command:
fabro run run.toml

Minimal example

A run config needs at minimum a schema version and a goal:
run.toml
_version = 1

[workflow]
graph = "workflow.fabro"

[run]
goal = "Implement the login feature"
FieldRequiredDescription
_versionNo (defaults to 1)Schema version. Must be 1 in the first pass.
[workflow].graphNoPath to the Graphviz workflow file, relative to the TOML file’s directory. Defaults to workflow.fabro.
[run].goalNoWhat the workflow should accomplish. Passed to agents and used in retrospectives. Can also be provided via --goal CLI flag or Graphviz graph goal attribute.
Goal precedence: CLI --goal > [run].goal > Graphviz graph attribute.

Full example

run.toml
_version = 1

[workflow]
graph = ".fabro/workflows/ci.fabro"

[run]
goal = "Run the CI pipeline"
working_dir = "/tmp/workdir"

[run.model]
name = "claude-sonnet-4-5"
fallbacks = ["openai", "gemini"]

[[run.prepare.steps]]
script = "git clone https://github.com/fabro-sh/fabro repo"

[[run.prepare.steps]]
script = "cd repo && npm install"

[run.sandbox]
provider = "daytona"
preserve = false

[run.sandbox.daytona]
auto_stop_interval = 60

[run.sandbox.daytona.labels]
project = "fabro"
env = "ci"

[run.sandbox.daytona.snapshot]
name = "node-20"
cpu = 4
memory = "8GB"
disk = "20GB"
dockerfile = "FROM node:20-slim\nRUN apt-get update && apt-get install -y git"

[run.sandbox.env]
API_KEY = "{{ env.MY_API_KEY }}"
NODE_ENV = "production"

[run.checkpoint]
exclude_globs = ["**/node_modules/**", "**/.cache/**"]

[run.inputs]
repo_name = "fabro"
repo_url = "https://github.com/fabro-sh/fabro"

[run.artifacts]
include = ["test-results/**", "playwright-report/**"]

[run.agent.mcps.playwright]
type = "sandbox"
command = ["npx", "@playwright/mcp@latest", "--port", "3100", "--headless"]
port = 3100

[run.pull_request]
enabled = true
draft = false

[[run.hooks]]
id = "pre-check"
event = "stage_start"
script = "./scripts/pre-check.sh"
blocking = true
sandbox = false

[[run.hooks]]
event = "run_complete"
script = "echo done"

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
[run.model]
name = "claude-sonnet-4-5"
FieldDescription
nameModel ID or alias (e.g. claude-sonnet-4-5, opus, gemini-pro). See Models.
providerProvider name (optional — auto-inferred from the model catalog). Only needed for models not in the catalog or to force a specific provider.
fallbacksOrdered list of model references to try when the primary is unavailable. Entries can be bare provider tokens ("openai"), bare model aliases, or qualified "provider/model" references.

Fallbacks with splice

Use the reserved "..." marker in fallbacks to splice in the inherited list from lower-precedence layers:
run.toml
[run.model]
# Prepend "anthropic" to whatever fallbacks the project config already defines.
fallbacks = ["anthropic", "..."]

[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
[[run.prepare.steps]]
script = "pip install -r requirements.txt"

[[run.prepare.steps]]
script = "npm install"
FieldDescription
scriptShell-evaluated command (runs through sh -c).
commandArgv-style command, mutually exclusive with script.
envAdditional environment variables for this step.
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.

[run.sandbox]

Configure how agent tools (bash, file edits) are executed.
run.toml
[run.sandbox]
provider = "docker"
preserve = true
FieldDescription
providerSandbox mode: local (default), docker, or daytona.
preserveWhen true, keep the sandbox alive after the run finishes. Useful for debugging.
devcontainerWhen true, use the repo’s devcontainer.json to configure the sandbox. See Devcontainers.

[run.sandbox.daytona]

Additional settings when using the Daytona cloud sandbox:
run.toml
[run.sandbox.daytona]
auto_stop_interval = 60

[run.sandbox.daytona.labels]
project = "fabro"
env = "staging"

[run.sandbox.daytona.snapshot]
name = "my-snapshot"
cpu = 4
memory = "8GB"
disk = "20GB"
dockerfile = "FROM rust:1.85-slim-bookworm\nRUN apt-get update"
# Or reference an external Dockerfile:
# dockerfile = { path = "./Dockerfile" }
FieldDescription
auto_stop_intervalMinutes of inactivity before the sandbox auto-stops.
labelsKey-value labels attached to the sandbox for filtering and identification. Labels merge across layers (sticky merge-by-key).
snapshot.nameSnapshot name to create or use for the sandbox.
snapshot.cpuCPU cores for the snapshot (integer).
snapshot.memoryMemory size using human-readable units: "8GB", "16GiB", or bare integers that default to GB.
snapshot.diskDisk size using the same units as memory.
snapshot.dockerfileDockerfile content (inline string) or path ({ path = "..." }) for building the snapshot image. Paths are resolved relative to the TOML file’s directory.
networkNetwork access mode: "allow_all" (default), "block", or { allow_list = ["..."] }. See Sandboxing.

[run.sandbox.local]

Additional settings when using the local sandbox:
run.toml
[run.sandbox.local]
worktree_mode = "always"
FieldDescription
worktree_modeWhen to create a git worktree for the run: always, clean (default — only when the working tree is clean), dirty (also when dirty), or never.

[run.sandbox.env]

Pass environment variables into sandbox command and agent execution. Values can be literal strings or host environment references using {{ env.VARNAME }} syntax:
run.toml
[run.sandbox.env]
API_KEY = "{{ env.MY_API_KEY }}"
NODE_ENV = "production"
SERVICE_URL = "https://api.{{ env.REGION }}.example.com"
SyntaxDescription
"literal"Static value passed as-is
"{{ env.VARNAME }}"Whole-value reference resolved from the host environment at consumption time
"prefix-{{ env.X }}-suffix"Substring interpolation; multiple tokens per string are supported
Missing host variables produce a hard error pointing at the specific field and unresolved token. run.sandbox.env is a sticky merge-by-key map: entries from all layers combine, with higher-precedence layers overriding individual keys.

[run.checkpoint]

Configure how git checkpoint commits behave.
run.toml
[run.checkpoint]
exclude_globs = ["**/node_modules/**", "**/.cache/**", "**/dist/**"]
FieldDescription
exclude_globsGlob patterns for files to exclude from checkpoint commits. Uses git pathspec :(glob,exclude) syntax.
exclude_globs replaces across layers — the higher-precedence layer wins wholesale.

[run.inputs]

Define inputs that are expanded into the Graphviz source before the graph is parsed. See Variables for the full reference.
run.toml
[run.inputs]
repo_name = "fabro"
repo_url = "https://github.com/fabro-sh/fabro"
language = "rust"
Inputs can be used anywhere in the Graphviz file with {{ inputs.name }} syntax:
c-i.fabro
digraph CI {
    graph [goal="Run tests for {{ inputs.repo_name }}"]
    clone [shape=parallelogram, script="git clone {{ inputs.repo_url }} repo"]
    test  [label="Test", prompt="Run the {{ inputs.language }} test suite."]
}
If a workflow template references an undefined input like {{ inputs.langauge }}, Fabro raises an error immediately. [run.inputs] replaces wholesale across layers. Unlike labels, inputs do not merge by key — the highest-precedence layer that sets inputs wins its entire map.

[run.artifacts]

Configure automatic collection of test artifacts (Playwright reports, JUnit XML, screenshots, etc.) from the execution environment after each stage.
run.toml
[run.artifacts]
include = ["test-results/**", "playwright-report/**", "*.trace.zip"]
FieldDescription
includeGlob patterns for files to collect as assets. Matched against the working directory after each stage completes.
Artifact collection is opt-in — when no [run.artifacts] section is present, no file scanning occurs.

[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
[run.agent.mcps.playwright]
type = "sandbox"
command = ["npx", "@playwright/mcp@latest", "--port", "3100", "--headless", "--browser", "chromium"]
port = 3100
startup_timeout = "60s"
tool_timeout = "2m"
FieldDescriptionDefault
typeTransport type: "stdio", "http", or "sandbox".
script(stdio, sandbox) Shell-evaluated startup command, mutually exclusive with command.
command(stdio, sandbox) Argv array: executable + arguments.
port(sandbox) Port the server listens on inside the sandbox.
url(http) The MCP server endpoint URL.
env(stdio, sandbox) Additional environment variables.{}
headers(http) Optional HTTP headers for authentication.{}
startup_timeoutMax duration for server startup + MCP handshake (e.g. "10s", "1m")."10s"
tool_timeoutMax duration for a single tool call."60s"
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.
run.toml
[run.pull_request]
enabled = true
draft = true
auto_merge = false
merge_strategy = "squash"
FieldDescription
enabledWhen true, Fabro creates a PR from the agent’s working branch after a successful run. Default: false.
draftWhen true, the PR is created as a draft pull request. Default: true.
auto_mergeWhen true, enables GitHub auto-merge on the created PR. Implies draft = false since GitHub doesn’t allow auto-merge on draft PRs. The repository must have auto-merge enabled in GitHub settings. Default: false.
merge_strategyMerge method when auto_merge is enabled: squash (default), merge, or rebase.

[[run.hooks]]

Define hooks that run in response to lifecycle events. Each hook is a TOML array entry:
run.toml
[[run.hooks]]
id = "pre-check"
name = "Pre-check script"
event = "stage_start"
script = "./scripts/pre-check.sh"
matcher = "agent_loop"
blocking = true
timeout = "30s"
sandbox = false
FieldDescription
idOptional merge identity. Hooks with the same id replace each other across layers.
nameOptional display name for the hook.
eventLifecycle event: run_start, run_complete, stage_start, stage_complete, etc.
scriptShell-evaluated command (equivalent to the old type = "command" shorthand).
commandArgv-style command (alternative to script).
matcherRegex matched against node ID or handler type. Limits which stages trigger this hook.
blockingWhether the hook must complete before execution continues. Defaults vary by event.
timeoutHuman-readable hook timeout (e.g. "30s", "1m"). Default: "60s".
sandboxRun inside the sandbox (true, default) or on the host (false).
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:
project/
  runs/
    ci.toml       # [workflow] graph = "ci.fabro"
    ci.fabro
Absolute paths are used as-is.

Precedence

Settings can come from multiple sources. Fabro resolves them in this order (first match wins):
SourcePriority
Node-level stylesheetHighest
CLI flags (--model, --provider, --sandbox)
Run config TOML (workflow.toml or equivalent)
Project defaults (.fabro/project.toml)
Machine defaults (~/.fabro/settings.toml)
Graphviz graph attributes (default_model, default_provider)
Built-in defaultsLowest
Stylesheet rules on individual nodes always take priority over run config values.

Project defaults (.fabro/project.toml)

The .fabro/project.toml project config can set default values for any of the [run.*] sections described above. These defaults apply to all runs in the project unless the workflow config overrides them:
.fabro/project.toml
_version = 1

[run.model]
name = "claude-sonnet-4-5"

[run.sandbox]
provider = "daytona"

[run.sandbox.daytona.snapshot]
name = "my-project-snapshot"
Project defaults and workflow config values merge per the normative merge matrix: most fields merge by field (higher-precedence wins per key), run.inputs replaces wholesale, run.sandbox.env sticky-merges by key, and run.prepare.steps replaces whole-list.

Machine defaults

When running locally, the machine defaults at ~/.fabro/settings.toml can set run-scoped defaults too. Same merge rules apply.

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], [features], or _version is rejected with a targeted rename hint pointing at the v2 replacement path.
  • Variable check — Any undefined workflow template variable in the Graphviz file produces an error.
Use fabro preflight to validate a run config without executing it:
fabro preflight run.toml