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, setup commands, variables, 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 requires two fields:
run.toml
version = 1
graph = "workflow.fabro"
goal = "Implement the login feature"
FieldRequiredDescription
versionYesConfig format version. Must be 1.
graphYesPath to the DOT workflow file, resolved relative to the TOML file’s directory.
goalNoWhat the workflow should accomplish. Passed to agents and used in retrospectives. Can also be provided via --goal CLI flag or DOT graph goal attribute.
Goal precedence: CLI --goal > TOML goal > DOT graph attribute.

Full example

run.toml
version = 1
goal = "Run the CI pipeline for $repo_name"
graph = "fabro/workflows/ci.fabro"
directory = "/tmp/workdir"

[llm]
model = "claude-sonnet-4-5"

[llm.fallbacks]
anthropic = ["gemini", "openai"]
gemini = ["anthropic", "openai"]

[setup]
commands = ["git clone $repo_url repo", "cd repo && npm install"]
timeout_ms = 120000

[sandbox]
provider = "daytona"
preserve = false

[sandbox.daytona]
auto_stop_interval = 60

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

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

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

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

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

[assets]
include = ["test-results/**", "playwright-report/**"]

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

[pull_request]
enabled = true
draft = false

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

[[hooks]]
event = "run_complete"
command = "echo done"

Sections

[llm]

Override the default model and provider for all nodes that don’t have an explicit model assigned via a stylesheet.
run.toml
[llm]
model = "claude-sonnet-4-5"
FieldDescription
modelModel 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.

[llm.fallbacks]

Map each provider to an ordered list of fallback providers. When the primary provider is unavailable, Fabro tries the fallbacks in order:
run.toml
[llm.fallbacks]
anthropic = ["gemini", "openai"]
gemini = ["anthropic", "openai"]

[setup]

Shell commands to run before the workflow starts. Use this to clone repositories, install dependencies, or prepare the environment.
run.toml
[setup]
commands = ["pip install -r requirements.txt", "npm install"]
timeout_ms = 60000
FieldDescription
commandsList of shell commands, executed sequentially via sh -c.
timeout_msPer-command timeout in milliseconds. Default: 300000 (5 minutes).
Each command must exit with status 0. If any command fails or times out, the run aborts before the workflow starts.

[sandbox]

Configure how agent tools (bash, file edits) are executed.
run.toml
[sandbox]
provider = "docker"
preserve = true
FieldDescription
providerSandbox mode: local (default), docker, daytona, ssh, or exe.
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.

[sandbox.daytona]

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

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

[sandbox.daytona.snapshot]
name = "my-snapshot"
cpu = 4
memory = 8
disk = 20
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.
snapshot.nameSnapshot name to create or use for the sandbox.
snapshot.cpuCPU cores for the snapshot.
snapshot.memoryMemory in GB for the snapshot.
snapshot.diskDisk in GB for the snapshot.
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.

[sandbox.local]

Additional settings when using the local sandbox:
run.toml
[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.

[sandbox.ssh]

Additional settings when using the SSH sandbox:
run.toml
[sandbox]
provider = "ssh"

[sandbox.ssh]
destination = "user@myserver"
working_directory = "/home/user/workspace"
FieldRequiredDescription
destinationYesSSH destination — user@host, a hostname, or an SSH alias from ~/.ssh/config.
working_directoryYesAbsolute path to the working directory on the remote host.
config_fileNoPath to a custom SSH config file (e.g. for non-default keys or jump hosts).
preview_url_baseNoBase URL for port previews (e.g. "http://myserver"). When set, preview URLs are {preview_url_base}:{port} instead of localhost.
See SSH sandbox for full details.

[sandbox.exe]

Additional settings when using the exe.dev cloud sandbox:
run.toml
[sandbox]
provider = "exe"

[sandbox.exe]
image = "my-custom-image:latest"
FieldDescription
imageCustom container image for the exe.dev VM. Optional — uses the exe.dev default when omitted.

[sandbox.env]

Pass environment variables into sandbox command and agent execution. Values can be literal strings or host environment passthrough using ${env.VARNAME} syntax:
run.toml
[sandbox.env]
API_KEY = "${env.MY_API_KEY}"
NODE_ENV = "production"
SyntaxDescription
"literal"Static value passed as-is
"${env.VARNAME}"Resolved from the host environment at load time. Missing vars produce a hard error.
Host env references must be whole-value only — partial interpolation like "prefix-${env.X}" is not supported. Sandbox env vars from server.toml defaults and the run config are merged, with the run config winning on key collisions.

[checkpoint]

Configure how git checkpoint commits behave.
run.toml
[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 from server.toml defaults and the run config are merged (union, deduplicated).

[vars]

Define variables that are expanded into the DOT source before the graph is parsed. See Variables for the full reference.
run.toml
[vars]
repo_name = "fabro"
repo_url = "https://github.com/fabro-sh/fabro"
language = "rust"
Variables can be used anywhere in the DOT file with $name syntax:
c-i.fabro
digraph CI {
    graph [goal="Run tests for $repo_name"]
    clone [shape=parallelogram, script="git clone $repo_url repo"]
    test  [label="Test", prompt="Run the $language test suite."]
}
If a $variable in the DOT file has no matching entry in [vars], Fabro raises an error immediately. A bare $ not followed by an identifier (e.g. costs $5) is left as-is.

[assets]

Configure automatic collection of test artifacts (Playwright reports, JUnit XML, screenshots, etc.) from the execution environment after each stage.
run.toml
[assets]
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.
Asset collection is opt-in — when no [assets] section is present, no file scanning occurs. This avoids the overhead of scanning large working directories when assets aren’t needed.

[mcp_servers]

Configure MCP servers available to agent stages during the workflow run. Each server is a named TOML table. All three transport types are supported: stdio, http, and sandbox.
run.toml
[mcp_servers.playwright]
type = "sandbox"
command = ["npx", "@playwright/mcp@latest", "--port", "3100", "--headless", "--browser", "chromium"]
port = 3100
startup_timeout_secs = 60
tool_timeout_secs = 120
FieldDescriptionDefault
typeTransport type: "stdio", "http", or "sandbox".
command(stdio, sandbox) 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_timeout_secsMax seconds for server startup + MCP handshake.10
tool_timeout_secsMax seconds for a single tool call.60
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.

[pull_request]

Automatically open a GitHub pull request when the workflow run completes successfully. Requires a GitHub App to be configured.
run.toml
[pull_request]
enabled = true
draft = true
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.

[[hooks]]

Define hooks that run in response to lifecycle events. Each hook is a TOML array entry:
run.toml
[[hooks]]
name = "pre-check"
event = "stage_start"
command = "./scripts/pre-check.sh"
matcher = "agent_loop"
blocking = true
timeout_ms = 30000
sandbox = false
FieldDescription
nameOptional display name for the hook.
eventLifecycle event: run_start, run_complete, stage_start, stage_complete.
commandShell command to execute (shorthand for type = "command").
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.
timeout_msHook timeout in milliseconds. Default: 60000 (60s).
sandboxRun inside the sandbox (true, default) or on the host (false).
See Hooks for hook types beyond simple commands (HTTP, prompt, agent).

Top-level fields

In addition to the sections above, two optional top-level fields are available:
FieldDescription
directoryWorking directory for the run. Defaults to the current directory.

Graph path resolution

The 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       # 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
Run config TOML
CLI flags (--model, --provider, --sandbox)
Server defaults (~/.fabro/server.toml)
DOT graph attributes (default_model, default_provider)
Built-in defaultsLowest
For model and provider specifically, the precedence is: CLI flags > TOML config > server defaults > DOT graph attributes > built-in defaults. Stylesheet rules on individual nodes always take priority over all of these.

Server defaults

When running via fabro serve, the server config at ~/.fabro/server.toml can set default values for [llm], [setup], [sandbox], and [vars]. These defaults are applied to every run unless the run config overrides them. For variables, defaults and run config are merged — the run config wins on key collisions:
# ~/.fabro/server.toml
[vars]
default_key = "from_server"
shared = "from_server"

# run.toml
[vars]
shared = "from_run"       # wins
task_key = "from_run"
The same merge behavior applies to Daytona labels. All other fields use simple “first non-empty wins” precedence.

Validation

Fabro validates the run config when it loads:
  • Version check — Only version = 1 is accepted. Other versions are rejected immediately.
  • Required fieldsversion and graph are required. goal is optional (can be provided via --goal or DOT graph attribute).
  • Unknown fields — Extra fields not listed above are rejected (deny_unknown_fields).
  • Variable check — Any $variable in the DOT file without a matching [vars] entry produces an error.
Use --preflight to validate a run config without executing it:
fabro run run.toml --preflight