Workflow
A workflow is the top-level configuration file that tells Loom what to run, in what order, and under what conditions. Every loom run begins by reading a single workflow file — making it the single source of truth for your entire CI/CD pipeline.
Why workflows matter
One file defines your full execution graph — stages, jobs, steps, variables, and provider configuration — in a single declarative YAML document. This gives you:
- One file to review when something breaks or changes.
- One file to validate before committing (
loom check). - One file to diff when debugging behavior changes between runs.
Unlike pipeline systems that scatter configuration across multiple files and hidden defaults, a Loom workflow keeps every decision visible and auditable in one place.
Workflow file location
| Setting | Value |
|---|---|
| Default path | .loom/workflow.yml |
| Override | --workflow <path> flag on any loom command |
| Schema version | v1 (the only supported version) |
Anatomy of a workflow
A workflow file is a YAML mapping with a fixed set of root-level keys:
| Key | Required | Purpose |
|---|---|---|
version | Yes | Schema version — must be the exact string "v1" |
stages | Yes | Ordered list of stage names that define execution phases |
variables | No | Workflow-level environment variables (defaults for all jobs) |
default | No | Default job settings inherited by all jobs via deep merge |
include | No | List of local template files to include ({local: .loom/templates/name.yml}) |
workflow | No | Workflow-level rules — currently supports rules with {if: ...} conditions |
Any other top-level key that matches the job name pattern (^[a-z][a-z0-9_-]{0,63}$) is treated as a job definition. Template jobs (names starting with .) are used for inheritance via extends but are not executed directly.
Minimal example
version: v1
stages: [ci]
check:
stage: ci
target: linux
script:
- echo "hello from Loom"
This defines one stage (ci) and one job (check) that runs a single shell command.
Multi-stage example
version: v1
stages: [build, test, deploy]
variables:
APP_ENV: "staging"
default:
target: linux
compile:
stage: build
script:
- make build
unit-tests:
stage: test
image: golang:1.22
script:
- go test ./...
integration:
stage: test
needs: [compile]
image: golang:1.22
script:
- go test -tags=integration ./...
ship:
stage: deploy
script:
- make deploy
Loom runs all build jobs before test jobs, and all test jobs before deploy jobs. Within a stage, jobs without needs dependencies execute alphabetically by name. The integration job also waits for compile to finish because of its explicit needs dependency.
The workflow lifecycle
A workflow passes through three phases, each answering a different question:
loom check loom compile loom run --local
┌───────────┐ ┌─────────────┐ ┌────────────────┐
│ "Is this │ yes │ "What will │ ok │ "What │
│ valid?" │──────► │ Loom run?" │─────► │ happened?" │
└───────────┘ └─────────────┘ └────────────────┘
Schema + Graph IR Runtime logs
static (compiled JSON + receipt
validation representation)
| Phase | Command | Question it answers | Output |
|---|---|---|---|
| Validate | loom check | Is this workflow valid? | Pass/fail with error paths like WF_SCHEMA_V1 /my-job/target: ... |
| Compile | loom compile | What will Loom execute? | Graph IR — a JSON representation of stages, nodes, edges, and resolved variables |
| Execute | loom run --local | What happened during execution? | Runtime logs under .loom/.runtime/logs/<run_id>/ and a receipt under .loom/.runtime/receipts/ |
Start with loom check — it is the fastest feedback loop. Run it before every commit to catch schema errors, missing keys, and invalid references without waiting for execution.
Naming rules
The schema enforces strict naming conventions to keep workflows machine-parseable and consistent:
| Entity | Pattern | Max length | Example |
|---|---|---|---|
| Stage name | ^[a-z][a-z0-9_-]{0,31}$ | 32 chars | ci, build-assets, deploy_prod |
| Job name | ^[a-z][a-z0-9_-]{0,63}$ | 64 chars | unit-tests, lint_go |
| Template job | ^\.[a-z][a-z0-9_-]{0,63}$ | 65 chars (including .) | .base-job |
| Variable key | ^[A-Z_][A-Z0-9_]*$ | No hard limit | APP_ENV, CI_DEBUG |
Common pitfalls
loom check fails with "unknown field" or "schema validation error"
- Cause: Your YAML uses keys that are not in the workflow schema v1, or values have the wrong type.
- Fix: Compare your YAML to the root-level key table above. Variable values must be quoted strings —
"3"not3. Job keys must come from the allowed set:stage,target,script,extends,needs,image,runner_pool,variables,secrets,invariant,cache,services.
Includes or templates don't resolve
- Cause: Template files are outside
.loom/templates/or use..path traversal. - Fix: Ensure
include.localpaths point to files under.loom/templates/with a.ymlor.yamlextension. See Includes & templates.
Jobs run in an unexpected order
- Cause: Misunderstanding stage ordering or the
needsdependency mechanism. - Fix: Loom executes stages in the order declared in
stages:. Within a stage, jobs withoutneedsdependencies are ordered alphabetically by name. Use explicitneedsto declare cross-job dependencies. Runloom compileto inspect the resolved execution graph. See Stages, jobs, and steps.
What to read next
- Structure your workflow: Stages, jobs, and steps
- Understand execution environments: Providers
- Parameterize with variables: Variables
- Full syntax reference: Syntax v1, Workflow schema v1
- Run your first workflow: Hello Loom