gc sling my-agent "do this thing". Real workflows have multiple steps with dependencies between them. A
formula captures that whole job as a unit and dispatches it as one.
A formula is a TOML file that records how a piece of work should be done — a
collection of steps with dependencies, variables, and optional control flow. It
isn’t the work itself (that’s a bead); it’s the reusable method. Running a
formula produces a workflow: a graph of step beads that Gas City’s
orchestrator routes to agents, gates on dependencies, retries on failure, and
drives to completion in the background. You write down the method once; the
orchestrator coordinates the doing — fanning ready steps out to as many agents as
you have.
A simple formula
Formula files use the.toml extension and live in your city’s
formulas/ directory. To follow along, write a pancakes recipe into that
directory:
[requires] block opts this formula into the formulas v2 graph
compiler: every step becomes its own independently routable bead, and runtime
constructs like checks and retries (below) become available. Every formula in
this tutorial declares it, and yours should too. The reference compares the
contracts side by side:
Choosing a Compiler Contract.
The needs field declares dependencies between sibling steps. dry and wet
have no needs, so they run in parallel; combine waits for both; cook
waits for combine; serve waits for cook. Without needs, every step
could run at any time — a messy kitchen, not a stack of pancakes.
Inspecting formulas
gc formula list enumerates the formulas your city can see.
mol-* entries are built-ins from the explicit imports gc init wrote —
the bundled core and bd/dolt system packs plus the default gascity
methodology pack (worker scaffolds, Dolt maintenance, and planning or
implementation workflows). The set may grow as pack releases change;
pancakes is the one you just defined.
To see the compiled recipe for a specific formula:
gc formula show compiles the formula — arranging steps and dependencies — and
displays the result. You wrote five steps, but the recipe shows six: the
compiler appends a workflow-finalize control step that depends on the last
steps in your graph. Agents never work on it; the orchestrator completes it once
everything upstream is done, recording the workflow’s outcome and closing it.
For the next few examples, keep the mayor from earlier tutorials and add a
generic worker so you have a second execution target:
claude, this city-scoped worker does not
need an agent.toml yet. Add one later if you want provider, model, or
directory overrides.
Instantiating a formula
Slinging a formula compiles it, materializes the root and every step as beads, and routes the resulting workflow to an agent — all in one command.--nudge to poke the
target immediately.
This is a different verb than Tutorial 01’s sling. There, sling created a bead
from your prompt and attached a workflow from the agent’s default formula
(Attached workflow ...). Here you sling the formula (the method) directly, so
sling starts a workflow, materializing its steps into beads (the work).
v1 formulas: wisps instead of workflows
v1 formulas: wisps instead of workflows
With the v1 compiler, slinging a formula creates a wisp — an ephemeral
molecule, a container bead holding its steps as children. You’ll see that
vocabulary in older formulas and messages; v2 formulas start workflows.
Tutorial 06 shows how both shapes land in the bead store.
gc formula cook: same compilation, same beads,
no routing.
Created: 7 is the root plus your five steps plus the finalize step, each with
its own bead ID. Slinging the cooked root routes it like any other bead — and
because that’s a plain bead sling, you get an auto-convoy tracking it (the
--formula path doesn’t create one).
Cook in the scope where the work belongs. Running gc formula cook inside a rig
directory creates the beads in that rig’s store (mp- prefixes for
my-project), and gc sling refuses to route a rig’s bead to an agent that
reads a different store. We cooked in the city, so the city-scoped worker can
take it.
Variables
Like a function, a formula can be parameterized. Declare variables in a[vars]
section and reference them as {{name}} in step titles, descriptions, and other
text fields. They expand at cook or sling time — placeholders become concrete
values in the resulting beads.
In the simplest case, a variable is a name with a default value:
cook doesn’t echo the substituted titles. To preview the expansion, use gc formula show:
name = "world" makes "world" the default; without --var name, the variable
falls back to it. A variable with no default and no required flag leaves the
literal text {{name}} in the output — rarely what you want, so always give a
default or mark it required.
Variables can carry richer definitions:
A more complete example:
--var:
gc formula show previews the substituted recipe and the declared variables;
required ones get their own section:
cook or sling. That late binding
is what makes formulas reusable across contexts.
The dependency graph
needs gets more interesting as formulas grow. Steps fan out — multiple steps
depending on the same predecessor run in parallel:
test and review both wait for implement but can run in parallel with
each other. The dependency graph is a DAG — the v2 compiler rejects
cycles at compile time.
Nested steps
When a formula gets large, you can group related steps under a parent:backend.api, backend.db). The grouping is purely
organizational: dependencies connect exactly the steps you name. needs = ["backend"] waits for the backend step itself, not its children — to wait for
the sub-steps, list them: needs = ["api", "db"]. You always reference steps by
their raw id; the compiler maps those to the namespaced recipe IDs.
Because you reference by raw id, those IDs must be unique across the whole
formula, children included — two parents can’t each have a child called test;
validation rejects the duplicate.
Control flow
needs and children shape the order of work. Four more constructs control
whether a step runs at all, and how many times. Two resolve at compile time
(when you cook); two run at runtime (while the workflow executes):
Conditions
condition includes or excludes a step based on a variable set at sling or cook
time.
{{var}} == value, {{var}} != value), plus truthiness — a bare {{var}} includes the step unless the value
is empty, false, 0, no, or off; !{{var}} inverts that. The variable is
substituted first, then compared as a string. For richer branching, use multiple
variables and conditions across steps.
gc formula show shows conditions taking effect. deploy-flow is an
unconditional build step plus the conditional deploy above:
Loops
A step can wrap a body of sub-steps that execute multiple times:retry-deploy — with the formula line and
[requires] block, like every formula in this tutorial. The body is expanded
at cook time into three sequential iterations:
count loop can’t end early. A loop takes exactly
one of count, until, or range; range = "1..3" with var = "n" is a
compile-time cousin of count that exposes {n} for substitution in the body.
When you mean “keep trying until it works,” use Check below.
count accepts an integer literal only — template variables such as {{var}} are
not supported in count. For a variable-driven iteration count, use
range = "1..{n}" with var = "n" instead.The until loop and its caveat
The until loop and its caveat
An The caveat: nothing re-runs the body yet. Cooking validates the condition, but no
component in the current release — v1 or v2 — reads it back at runtime, so an
until loop expands one iteration at compile time and stamps the condition
(plus a required max budget) on it:until loop runs exactly one iteration. Treat it as declared intent; use Check
for keep-trying-until-it-passes behavior today. The condition is not the
{{var}} syntax from Conditions — it’s an expression over step state, like
probe.status == 'complete'. The
Loops spec section covers the
grammar.Check
Conditions and loops are decided when you cook. Check makes a decision at runtime: it runs a validation script after the agent finishes a step. If the script passes, the step is done; if not, another iteration is spawned and the agent tries again — a runtime feedback loop, not a compile-time expansion.[requires] block, compilation fails with an
error telling you to add it. The runtime loop shows up in the compiled recipe:
implement ID. When an iteration closes, Gas
City runs scripts/verify.sh; exit 0 means done, non-zero spawns another
iteration — up to max_attempts total. If all attempts fail, the step fails.
One nonzero exit is special: 75 says the script could not reach the
infrastructure it needed to decide, so Gas City re-runs the check without
spending an attempt — see Check
in the spec.
Retry
Where Check decides pass/fail with a script,[steps.retry] handles the simpler
case — a step that sometimes fails for transient reasons and should just be
re-dispatched:
max_attempts. on_exhausted decides what happens when the budget runs
out — "hard_fail" (the default) fails the step, "soft_fail" records the
failure but lets the workflow continue.
The orchestrator owns these runtime loops, deciding when to re-dispatch, fan out,
or finalize without you in the session. More constructs exist: drain scatters
a convoy of work items into per-item runs, and on_complete/tally fan out
follow-up work over a step’s output and aggregate the results. The v2 spec’s
Runtime section covers the full
set.
What’s next
- The six primitives — the canonical model formulas and the work they produce build on
- Formula spec (v2) — the complete surface: every top-level key, every step field, and the v2 runtime constructs
- Beads — the universal work primitive underneath formulas, sessions, and everything else
- Orders — formulas with scheduling triggers for periodic dispatch