Skip to main content

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 # the grida command, with FX as grida fx (Node.js 24 or later)
pip install grida # the Python SDK, for nodes and builders you write in Python
npm install @grida/fx # in a project: the SDK for nodes and builders you write in TypeScript
grida fx doctor # checks the keys and tools your workflows need

grida carries the FX version it was released with; updating grida updates FX. No repository clone or Rust toolchain is needed. FX runs on macOS, and on Linux with glibc 2.28 or later, on x64 and arm64. On Windows, use WSL 2. Install the SDK of the language you write your own nodes in: one language per project is the usual choice, though a project may mix them.

This guide writes grida fx. You may also meet FX as grida-fx or python -m grida.fx: these are the same FX, started another way, and take the same commands (grida-fx plan is grida fx plan).

  • grida-fx is FX on its own, from the @grida/fx package, which is also the JavaScript SDK. Projects that depend on @grida/fx, and servers or CI images that need only FX, use it.
  • python -m grida.fx is FX from the Python package, for Python users who don't want Node (Running).

They share project files, the cache and run records. --version prints the FX version under any of them, so you can tell two installations apart. Setting them up, and source development, are in CONTRIBUTING.md.

One key for everything: Grida Gateway. Create a key at console.grida.co and set it as GG_API_KEY. It serves every route on Grida Gateway (@gg): text, agent turns, images, video, 3D models and rigs, billed to your organization's Grida AI credits, so there is no provider account to open for each model. Set it in the environment, or put it in a .env file in your project (GG_API_KEY=…, 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).

Already have provider accounts? FX also reads each provider's own key under its usual name (OPENAI_API_KEY, OPENROUTER_API_KEY, FAL_KEY, TRIPO_API_KEY, ELEVENLABS_API_KEY) for the routes on that provider; sound, speech and music need one, since GG has no route for them yet. No key at all? With Codex signed in to your ChatGPT account, or Grok Build signed in to a Grok subscription, images can come from your own Codex or your own Grok Build. AI providers compares the ways.

When FX needs Python​

The engine itself runs the paid built-in types (image.generate, structured.generate, …), fx/select@1 and every file fact. Your own nodes, builders and adapters run in their language: TypeScript ones on Node (below), Python ones on Python. Python runs:

  • your own Python nodes and builders, which import from grida.fx import node, Ctx;
  • your own Python adapters, which import from grida.fx import adapter: planning loads each one a project declares, and a live call on a route its provider serves runs in it;
  • the built-in local types, whatever language your own nodes are in: image.resize, image.crop, image.pad, image.mirror_repeat, image.check_alpha, image.check_size, json.merge, files.copy and package.

A project that uses any of them needs Python 3.11 or later 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. An older Python is refused before FX starts it (macOS's own /usr/bin/python3 is 3.9): run uv sync or pip install grida in the project, or set GRIDA_FX_PYTHON. A project whose steps are all paid built-ins, select and nodes of your own in TypeScript needs no Python, unless it declares a Python adapter of its own. The first workflow below uses image.check_alpha, so it needs Python.

When FX needs Node​

Your own nodes, builders, stand-ins and adapters in TypeScript (or JavaScript) run on Node, in the node host that @grida/fx carries; planning loads a project's adapters too. A project with any of them needs Node 24 or later (Bun and Deno cannot host nodes), and should install @grida/fx (npm install @grida/fx): your modules import node (or adapter) from it, and tsc checks them against it. FX hosts them with the @grida/fx the workflow's project would import, else the one the project you run from would import, else the one that started FX, such as the one grida fx carries. FX uses GRIDA_FX_NODE when it is set, else the Node that runs grida fx or the JavaScript SDK (Running), else node on PATH. An older Node is refused before FX starts it: install Node 24 or later, or set GRIDA_FX_NODE.

A project​

A project is a folder with an fx.yaml in it. Start a new one with:

npx grida fx init my-assets
cd my-assets
grida fx start --background
grida fx status --json

init writes a starter project into a new or empty folder: fx.yaml, a README.md that says how to install the CLI and connect an AI provider, an AGENTS.md and the FX skills (.agents/skills/) for coding agents, a .gitignore that keeps runs, the cache, .env and what Python and npm install out of git, an .env.example listing the key names, and workflows/icon.yaml, a one-step workflow that draws an icon once you pick a route. It installs nothing and starts nothing. On a terminal it asks which template to start from; --template NAME names one without asking, and grida fx init --list-templates lists them. A template is one of the examples, downloaded from the FX release you have, with the same agent files added. In a folder that already has an fx.yaml, it checks that file and writes nothing. It refuses any other folder that has files in it: to make an existing folder (a game repository, say) a project, write its fx.yaml yourself, holding fx: project/v1.

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. These commands need FX 0.2 or later; grida fx --version names yours.

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
package.json # "type": "module" and @grida/fx (TypeScript nodes only)
runs/ # every run, one folder each (created by FX)
.fx/cache/ # content-addressed results (created by FX)
.fx/service/ # local service state and catalog (created by FX)

The starter leaves routes and budgets commented out for you to choose. For the paid example below, set them:

# 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: openai/gpt-image-2.5-sunburst@gg
# 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, the provider declarations and the takes file (<id>.takes.yaml at your root) stay those of the project you run from.

Keep runs/, .fx/, .venv/ and node_modules/ 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 (openai/gpt-image-2.5-sunburst@gg at 1024x1024: 0.21–0.21 – 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 openai/gpt-image-2.5-sunburst@gg 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.