Getting started
FX builds generated assets the way a build system builds code:
- You describe the steps in a workflow file.
- FX plans the run offline, with a cost estimate.
- It runs only what changed.
- Every result is kept with a record of how it was made.
Install
npm install -g @grida/fx # the grida-fx command (or without installing: npx @grida/fx)
pip install grida # the Python SDK, for nodes and builders you write in Python
grida-fx doctor # checks the keys and tools your workflows need
Both packages are published as stable releases starting at 0.1.0 and carry the same engine.
No repository clone or Rust toolchain is needed. For source development, see
CONTRIBUTING.md.
FX runs on macOS, and on Linux with glibc 2.28 or later, on x64 and arm64. Native Windows is not
supported, because the engine's runner uses Unix process groups and signals: use WSL 2
(the platforms). Python users who don't want Node run
it as python -m grida.fx <verb> (python -m grida.fx doctor), or through the Python API
(Running). This guide writes grida-fx.
FX reads each provider's key under its usual name: OPENAI_API_KEY, OPENROUTER_API_KEY,
FAL_KEY, TRIPO_API_KEY, ELEVENLABS_API_KEY. Set them in the environment, or put them in a
.env file in your project (one NAME=value per line, and keep the file out of version
control); the environment wins. Keys never go in fx.yaml or a workflow, and FX never prints
them: only a --live run uses them, and grida-fx doctor says which ones it found, where, and
which routes they make usable (Running).
When FX needs Python
The engine itself runs the paid built-in types (image.generate, structured.generate, …),
fx/select@1 and every file fact. Everything else runs on Python:
- your own Python nodes and builders, which import
from grida.fx import node, Ctx; - the built-in local types too:
image.resize,image.crop,image.pad,image.mirror_repeat,image.check_alpha,image.check_size,json.merge,files.copyandpackage.
A project that uses any of them needs a Python with grida and Pillow installed
(pip install grida, which brings Pillow). FX uses GRIDA_FX_PYTHON when it is set, else
the .venv of the workflow's project, else the .venv of the project you run from, else the Python
that runs the SDK when you run through it (Running), else python3
on PATH. Only a project whose steps are all paid built-ins and select needs just the command.
The first workflow below uses image.check_alpha, so it needs Python.
A project
A project is a folder with an fx.yaml in it. In current source, the standard
setup is:
grida-fx init
grida-fx start --background
grida-fx status --json
init creates only a minimal fx.yaml; it preserves existing configuration and
does not install dependencies or start anything. start serves the bundled
dashboard at http://127.0.0.1:8787/. Add --open to open it, or omit
--background to keep the service in your terminal. grida-fx stop stops the
service without stopping workflows or deleting their records. Background mode
survives the launching command; it does not install crash or login supervision.
Check grida-fx --help first: older published versions may lack these commands.
As you author workflows and run them, a project can look like this:
my-assets/
fx.yaml # project settings
workflows/ # your workflow files
nodes/ # your own node types (optional)
prompts/ # prompt templates (optional)
inputs/ # files you feed in
runs/ # every run, one folder each (created by grida-fx)
.fx/cache/ # content-addressed results (created by grida-fx)
.fx/service/ # local service state and catalog (created by grida-fx)
The initializer leaves routes and budgets to you. For the paid example below, extend its configuration:
# fx.yaml
fx: project/v1
budget:
max_usd: 10 # the ceiling for any one run; --max-usd overrides it
routes: # which model serves each capability by default
image.generate: gpt-image-2.5-sunburst@openai
# workflows: [workflows, ../tools/art/workflows] # where a workflow id is looked for
grida-fx nodes image.generate lists the routes that can serve a capability: FX's built-in
route table, plus the tables your project lists under route_tables: (read after it, so their
entries win).
Where workflows are found. A workflow named by its id (grida-fx run icon) is looked for among
the .yaml and .yml files at the project root, then in workflows/ and every folder below it. A
monorepo that keeps its workflows elsewhere lists their folders under workflows:, searched in the
order given; each is relative to the project or absolute, and may lie outside the project. The list
replaces the default, so name workflows too if you keep workflows there as well. The project root
itself, or a folder above it, is refused, since it would search your runs and cache. A workflow in a
folder with an fx.yaml of its own keeps that project as its home: its ./ paths, node modules and
route defaults are that project's, with your routes: winning. The runs, the cache, the budget, the
route tables and the takes file (<id>.takes.yaml at your root) stay those of the project you run
from.
Keep runs/ and .fx/ out of source control. A project file is recommended,
not required for execution. For a one-off workflow, call run directly; use
--standalone inside an existing project to request an independent viewer for
that invocation. See Viewing.
Your first workflow
Generate one item icon on a transparent background. If the background isn't actually clean, draw it again.
# workflows/icon.yaml
fx: workflow/v1
id: icon
title: One item icon
inputs:
name: { type: string, description: "What the item is" }
steps:
draw:
uses: fx/image.generate@1
with:
prompt: "A single ${{ inputs.name }} game icon, centered, no text."
background: transparent
size: 1024x1024
clean:
uses: fx/image.check_alpha@1 # a built-in, deterministic judge
judges: draw
with: { image: "${{ steps.draw.outputs.image }}", expect: transparent }
on_reject:
regenerate: { max: 3 } # draw again, at most three takes
outputs:
icon: ${{ steps.draw.outputs.image }}
Plan, then run
grida-fx plan workflows/icon.yaml --name "copper lantern"
icon · 1 phase
phase 1 6 steps 1–3 provider calls $0.21 – $0.87
cached 0 of 3 known steps
estimate $0.21 – $0.87 ceiling $10.00
The plan counts every take regeneration may draw: three drawings and their three checks. The low
end is the one drawing that surely runs, at the low price of its size's tier; the high end is all
three, at the tier's high. The prices are the allowances of FX's built-in route table, in tiers by
size (gpt-image-2.5-sunburst@openai at 1024x1024: 0.29 a call), which a project's own
tables can override.
grida-fx run workflows/icon.yaml --live -- --name "copper lantern"
--live is required whenever a run may call a paid provider. Without it nothing is spent: local
steps still run, a paid call the cache already answered is replayed, and any other paid call
fails its step (image.generate on gpt-image-2.5-sunburst@openai is a paid call; run with --live). A live
run also needs a ceiling, here the project's budget:. The run's folder holds its outputs;
grida-fx inspect summarises it.
With the local service running, add --open to a plan or run to open its canvas.
The run URL stays available after execution finishes. A missing service does not
prevent execution; start it explicitly when you want browser inspection.
Run it again
Run the same command again and nothing is generated: every step is answered from the cache. Change
the workflow input name (after --) and only draw and clean run again. Rename a step, reorder your file, or change a
title, and nothing re-runs. Cost, cache and takes has the exact rule.
Don't like the icon? Ask for another take of that one step:
grida-fx reroll runs/icon/2026-10-02-1 draw # take 2 from now on; take 1 stays in the cache
grida-fx run workflows/icon.yaml --live -- --name "copper lantern" # draws take 2
grida-fx pick runs/icon/2026-10-02-2 draw 1 # changed your mind: back to take 1, free
reroll and pick write your choice into icon.takes.yaml beside the workflow; every run
reads it.